import { afterEach, describe, it } from '@ephox/bedrock-client'; import { Arr, Fun } from '@ephox/katamari'; import { assert } from 'chai'; import DOMUtils from 'tinymce/core/api/dom/DOMUtils'; import DomSerializer from 'tinymce/core/api/dom/Serializer'; import * as TrimHtml from 'tinymce/core/dom/TrimHtml'; import * as Zwsp from 'tinymce/core/text/Zwsp'; import * as ViewBlock from '../../module/test/ViewBlock'; declare const escape: any; describe('browser.tinymce.core.dom.SerializerTest', () => { const DOM = DOMUtils.DOM; const viewBlock = ViewBlock.bddSetup(); viewBlock.get().id = 'test'; afterEach(() => { viewBlock.update(''); }); const setTestHtml = (html: string) => DOM.setHTML('test', html); const getTestElement = () => DOM.get('test') as HTMLElement; it('Schema rules', () => { let ser = DomSerializer({ fix_list_elements: true }); ser.setRules('@[id|title|class|style],div,img[src|alt|-style|border],span,hr'); setTestHtml('testtest
'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'test
', 'Global rule' ); ser.setRules('*a[*],em/i[*],strong/b[*i*]'); setTestHtml('testtest2test3'); assert.equal(ser.serialize(getTestElement()), 'testtest2' + 'test3', 'Wildcard rules'); ser.setRules('br,hr,input[type|name|value],div[id],span[id],strong/b,a,em/i,a[!href|!name],img[src|border=0|title={$uid}]'); setTestHtml('

' + 'abc123123linkno'); assert.equal(ser.serialize(getTestElement()), '


' + 'abc123123link' + 'no
', 'Output name and attribute rules'); ser.setRules('img[src|border=0|alt=]'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), '', 'Default attribute with empty value'); ser.setRules('img[src|border=0|alt=],div[style|id],*[*]'); setTestHtml('
'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), '
' ); ser = DomSerializer({ valid_elements: 'img[src|border=0|alt=]', extended_valid_elements: 'div[id],img[src|alt=]' }); setTestHtml(''); assert.equal( ser.serialize(getTestElement()), '
' ); ser = DomSerializer({ invalid_elements: 'hr,br' }); setTestHtml('

'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), '' ); }); it('allow_unsafe_link_target (default)', () => { const ser = DomSerializer({ }); setTestHtml('ab'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'ab' ); setTestHtml('ab'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'ab' ); setTestHtml('ab'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'ab' ); setTestHtml('a'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'a' ); setTestHtml('a'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'a' ); }); it('allow_unsafe_link_target (disabled)', () => { const ser = DomSerializer({ allow_unsafe_link_target: true }); setTestHtml('ab'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), 'ab' ); }); it('format tree', () => { const ser = DomSerializer({ }); setTestHtml('a'); assert.equal( ser.serialize(getTestElement(), { format: 'tree' }).name, 'body' ); }); it('Entity encoding', () => { let ser: DomSerializer; ser = DomSerializer({ entity_encoding: 'numeric' }); setTestHtml('<>&" åäö'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), '<>&" åäö'); ser = DomSerializer({ entity_encoding: 'named' }); setTestHtml('<>&" åäö'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), '<>&" åäö'); ser = DomSerializer({ entity_encoding: 'named+numeric', entities: '160,nbsp,34,quot,38,amp,60,lt,62,gt' }); setTestHtml('<>&" åäö'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), '<>&" åäö'); ser = DomSerializer({ entity_encoding: 'raw' }); setTestHtml('<>&" åäö'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), '<>&"\u00a0\u00e5\u00e4\u00f6'); }); it('Form elements (general)', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules( 'form[method],label[for],input[type|name|value|checked|disabled|readonly|length|maxlength],select[multiple],' + 'option[value|selected],textarea[name|disabled|readonly]' ); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml('
'); assert.equal(ser.serialize(getTestElement()), '
'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); // Edge will add an empty input value so remove that to normalize test since it doesn't break anything assert.equal( ser.serialize(getTestElement()).replace(/ value=""/g, ''), '' ); }); it('Form elements (checkbox)', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('form[method],label[for],input[type|name|value|checked|disabled|readonly|length|maxlength],select[multiple],option[value|selected]'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); }); it('Form elements (select)', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('form[method],label[for],input[type|name|value|checked|disabled|readonly|length|maxlength],select[multiple],option[value|selected]'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); }); it('List elements', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('ul[compact],ol,li'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml('
  1. a
    1. b
    2. c
  2. e
'); assert.equal(ser.serialize(getTestElement()), '
  1. a
    1. b
    2. c
  2. e
'); }); it('Tables', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('table,tr,td[nowrap]'); setTestHtml('
'); assert.equal(ser.serialize(getTestElement()), '
'); setTestHtml('
'); assert.equal(ser.serialize(getTestElement()), '
'); setTestHtml('
'); assert.equal(ser.serialize(getTestElement()), '
'); setTestHtml('
'); assert.equal(ser.serialize(getTestElement()), '
'); }); it('Styles', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('*[*]'); setTestHtml('test'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), 'test'); }); it('Comments', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('*[*]'); setTestHtml(''); assert.equal(ser.serialize(getTestElement(), { getInner: true }), ''); }); it('Non HTML elements and attributes', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('*[*]'); ser.schema.addValidChildren('+div[prefix:test]'); setTestHtml('
test
'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), '
test
'); setTestHtml('test1Testtest2'); assert.equal(ser.serialize(getTestElement(), { getInner: true }), 'test1Testtest2'); }); it('Padd empty elements', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('#p'); setTestHtml('

test

'); assert.equal(ser.serialize(getTestElement()), '

test

 

'); }); it('Do not padd empty elements with padded children', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('#p,#span,b'); setTestHtml('

'); assert.equal(ser.serialize(getTestElement()), '

 

 

'); }); it('Remove empty elements', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('-p'); setTestHtml('

test

'); assert.equal(ser.serialize(getTestElement()), '

test

'); }); it('Script with non JS type attribute', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('script[type|language|src]'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), ''); }); // TODO: TINY-4627/TINY-8363 it.skip('Script with tags inside a comment with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml('// '); assert.equal( ser.serialize(getTestElement()).replace(/\r/g, ''), '// \n// ]]>' ); }); // TODO: TINY-4627/TINY-8363 it.skip('Script with tags inside a comment', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('script[type|language|src]'); setTestHtml('// '); assert.equal( ser.serialize(getTestElement()).replace(/\r/g, ''), '// ' ); }); it('Script with less than with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml('1 < 2;'); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), '// '); }); it('Script with less than', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('script[type|language|src]'); setTestHtml('1 < 2;'); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), '1 < 2;'); }); it('Script with type attrib and less than with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml('1 < 2;'); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), ''); }); it('Script with whitespace in beginning/end with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml('' ); }); it('Script with a HTML comment and less than with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with white space in beginning, comment and less than with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with comments and cdata with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with cdata with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script whitespace in beginning/end and cdata with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Whitespace preserve in pre', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('pre'); setTestHtml('
  
'); assert.equal(ser.serialize(getTestElement()), '
  
'); }); it('Script with src attr', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with block comment around cdata with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with html comment and block comment around cdata with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with line comment and html comment with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Script with block comment around html comment with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, element_format: 'xhtml' }); ser.setRules('script[type|language|src]'); setTestHtml(''); }); it('Protected blocks', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('noscript[test]'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml('
') + '-->'); assert.equal(ser.serialize(getTestElement()), ''); }); it('Style with whitespace at beginning with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, valid_children: '+body[style]', element_format: 'xhtml' }); ser.setRules('style'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), ''); }); it('Style with whitespace at beginning', () => { const ser = DomSerializer({ fix_list_elements: true, valid_children: '+body[style]' }); ser.setRules('style'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), ''); }); it('Style with cdata with element_format: xhtml', () => { const ser = DomSerializer({ fix_list_elements: true, valid_children: '+body[style]', element_format: 'xhtml' }); ser.setRules('style'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), ''); }); it('Style with cdata', () => { const ser = DomSerializer({ fix_list_elements: true, valid_children: '+body[style]' }); ser.setRules('style'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), ''); }); it('CDATA', () => { const ser = DomSerializer({ fix_list_elements: true, preserve_cdata: true }); ser.setRules('span'); setTestHtml('123abc'); assert.equal(ser.serialize(getTestElement()), '123]]>abc'); setTestHtml('123abc'); assert.equal(ser.serialize(getTestElement()).replace(/\r/g, ''), '123]]>abc'); }); it('BR at end of blocks', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('ul,li,br'); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); }); it('Map elements', () => { const ser = DomSerializer({ fix_list_elements: true }); ser.setRules('map[id|name],area[shape|coords|href|target|alt]'); DOM.setHTML( 'test', 'sun' ); assert.equal( ser.serialize(getTestElement()).toLowerCase(), 'sun' ); }); it('Custom elements', () => { const ser = DomSerializer({ custom_elements: 'custom1,~custom2', valid_elements: 'custom1,custom2,#p' }); document.createElement('custom1'); document.createElement('custom2'); setTestHtml('

c1c2

'); assert.equal(ser.serialize(getTestElement()), 'c1

c2

'); setTestHtml('

'); assert.equal(ser.serialize(getTestElement()), '

'); }); it('Remove internal classes', () => { const ser = DomSerializer({ valid_elements: 'span[class]' }); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); }); it('Restore tabindex', () => { const ser = DomSerializer({ valid_elements: 'span[tabindex]' }); setTestHtml(''); assert.equal(ser.serialize(getTestElement()), ''); }); it('Trailing BR (IE11)', () => { const ser = DomSerializer({ valid_elements: 'p,br' }); setTestHtml('

a



'); assert.equal(ser.serialize(getTestElement()), '

a

'); setTestHtml('a

'); assert.equal(ser.serialize(getTestElement()), 'a'); }); it('addTempAttr', () => { const ser = DomSerializer({}); ser.addTempAttr('data-x'); ser.addTempAttr('data-y'); setTestHtml('

a

'); assert.equal(ser.serialize(getTestElement(), { getInner: 1 }), '

a

'); assert.equal(TrimHtml.trimExternal(ser, '

a

'), '

a

'); }); it('addTempAttr same attr twice', () => { const ser1 = DomSerializer({}); const ser2 = DomSerializer({}); ser1.addTempAttr('data-x'); ser2.addTempAttr('data-x'); setTestHtml('

a

'); assert.equal(ser1.serialize(getTestElement(), { getInner: 1 }), '

a

'); assert.equal(TrimHtml.trimExternal(ser1, '

a

'), '

a

'); assert.equal(ser2.serialize(getTestElement(), { getInner: 1 }), '

a

'); assert.equal(TrimHtml.trimExternal(ser2, '

a

'), '

a

'); }); it('trim data-mce-bogus="all"', () => { const ser = DomSerializer({}); setTestHtml('a

b

c'); assert.equal(ser.serialize(getTestElement(), { getInner: 1 }), 'ac'); assert.equal(TrimHtml.trimExternal(ser, 'a

b

c'), 'ac'); }); it('zwsp should not be treated as contents', () => { const ser = DomSerializer({ }); setTestHtml('

' + Zwsp.ZWSP + '

'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), '

 

' ); }); it('nested bookmark nodes', () => { const ser = DomSerializer({ }); setTestHtml('

' + '' + '' + '' + '' + 'a' + '' + '' + '' + '

'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), '

a

' ); }); it('addNodeFilter/addAttributeFilter', () => { const ser = DomSerializer({ }); const nodeFilter = Fun.noop; const attrFilter = Fun.noop; ser.addNodeFilter('some-tag', nodeFilter); ser.addAttributeFilter('data-something', attrFilter); const lastNodeFilter = Arr.last(ser.getNodeFilters()).getOrDie('Failed to get filter'); const lastAttributeFilter = Arr.last(ser.getAttributeFilters()).getOrDie('Failed to get filter'); assert.equal(lastNodeFilter.name, 'some-tag', 'Should be the last registered filter element name'); assert.equal(lastNodeFilter.callbacks[0], nodeFilter, 'Should be the last registered node filter function'); assert.equal(lastAttributeFilter.name, 'data-something', 'Should be the last registered filter attribute name'); assert.equal(lastAttributeFilter.callbacks[0], attrFilter, 'Should be the last registered attribute filter function'); }); it('TINY-7847: removeNodeFilter/removeAttributeFilter', () => { const ser = DomSerializer({ }); const nodeFilter = Fun.noop; const attrFilter = Fun.noop; const numNodeFilters = ser.getNodeFilters().length; const numAttrFilters = ser.getAttributeFilters().length; ser.addNodeFilter('some-tag', nodeFilter); ser.addAttributeFilter('data-something', attrFilter); assert.lengthOf(ser.getNodeFilters(), numNodeFilters + 1, 'Number of node filters'); assert.lengthOf(ser.getAttributeFilters(), numAttrFilters + 1, 'Number of attribute filters'); ser.removeNodeFilter('some-tag', nodeFilter); ser.removeAttributeFilter('data-something', attrFilter); assert.lengthOf(ser.getNodeFilters(), numNodeFilters, 'Number of node filters'); assert.lengthOf(ser.getAttributeFilters(), numAttrFilters, 'Number of attribute filters'); }); it('TINY-9172: Should remove the internal data-mce-block attribute for transparent block elements', () => { const ser = DomSerializer({ }); setTestHtml('

block

'); assert.equal( ser.serialize(getTestElement(), { getInner: true }), '

block

' ); }); });