Skip to content

Commit

Permalink
Add tests GHS style integration plugin and for DomConverter style rep…
Browse files Browse the repository at this point in the history
…lacement.
  • Loading branch information
Maksymilian Barnaś committed Feb 8, 2022
1 parent daa10d4 commit 003e537
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 6 deletions.
4 changes: 3 additions & 1 deletion packages/ckeditor5-engine/src/view/domconverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -1636,7 +1636,9 @@ function hasBlockParent( domNode, blockElements ) {
function _logUnsafeElement( elementName ) {
if ( elementName === 'script' ) {
logWarning( 'domconverter-unsafe-script-element-detected' );
} else if ( elementName === 'style' ) {
}

if ( elementName === 'style' ) {
logWarning( 'domconverter-unsafe-style-element-detected' );
}
}
Expand Down
28 changes: 25 additions & 3 deletions packages/ckeditor5-engine/tests/view/domconverter/domconverter.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,16 @@ describe( 'DomConverter', () => {

expect( element.innerHTML ).to.equal( '<div>foo<script onclick="foo">bar</script></div>' );
} );

it( 'should keep style element', () => {
const element = document.createElement( 'p' );
const html = '<div>foo<style nonce="foo">bar</style></div>';

converter.renderingMode = 'data';
converter.setContentOf( element, html );

expect( element.innerHTML ).to.equal( '<div>foo<style nonce="foo">bar</style></div>' );
} );
} );

describe( 'editing pipeline', () => {
Expand Down Expand Up @@ -736,16 +746,28 @@ describe( 'DomConverter', () => {
);
} );

it( 'should warn when an unsafe element was detected and renamed', () => {
it( 'should warn when an unsafe script element was detected and renamed', () => {
const element = document.createElement( 'p' );
const html = '<div>foo<script class="foo-class" style="foo-style" data-foo="bar">bar</script></div>';

converter.setContentOf( element, html );

sinon.assert.calledOnce( warnStub );
sinon.assert.calledWithExactly( warnStub,
sinon.match( /^domconverter-unsafe-element-detected/ ),
sinon.match.has( 'unsafeElement', sinon.match.has( 'tagName', 'SCRIPT' ) ),
sinon.match( /^domconverter-unsafe-script-element-detected/ ),
sinon.match.string // Link to the documentation
);
} );

it( 'should warn when an unsafe style element was detected and renamed', () => {
const element = document.createElement( 'p' );
const html = '<div>foo<style class="foo-class" nonce="foo-nonce" data-foo="bar">bar</style></div>';

converter.setContentOf( element, html );

sinon.assert.calledOnce( warnStub );
sinon.assert.calledWithExactly( warnStub,
sinon.match( /^domconverter-unsafe-style-element-detected/ ),
sinon.match.string // Link to the documentation
);
} );
Expand Down
49 changes: 47 additions & 2 deletions packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,53 @@ describe( 'DomConverter', () => {

sinon.assert.calledOnce( warnStub );
sinon.assert.calledWithExactly( warnStub,
sinon.match( /^domconverter-unsafe-element-detected/ ),
sinon.match.has( 'unsafeElement', sinon.match.has( 'name', 'script' ) ),
sinon.match( /^domconverter-unsafe-script-element-detected/ ),
sinon.match.string // Link to the documentation
);
} );

it( 'should replace style with span and add special data attribute', () => {
const viewScript = new ViewElement( viewDocument, 'style' );
const viewText = new ViewText( viewDocument, 'foo' );
const viewP = new ViewElement( viewDocument, 'p', { class: 'foo' } );

viewP._appendChild( viewScript );
viewP._appendChild( viewText );

converter = new DomConverter( viewDocument, {
renderingMode: 'editing'
} );

const domP = converter.viewToDom( viewP, document );

expect( domP ).to.be.an.instanceof( HTMLElement );
expect( domP.tagName ).to.equal( 'P' );
expect( domP.getAttribute( 'class' ) ).to.equal( 'foo' );
expect( domP.attributes.length ).to.equal( 1 );

expect( domP.childNodes.length ).to.equal( 2 );
expect( domP.childNodes[ 0 ].tagName ).to.equal( 'SPAN' );
expect( domP.childNodes[ 0 ].getAttribute( 'data-ck-unsafe-element' ) ).to.equal( 'style' );
expect( domP.childNodes[ 1 ].data ).to.equal( 'foo' );
} );

it( 'should warn when an unsafe style was filtered out', () => {
const viewStyle = new ViewElement( viewDocument, 'style' );
const viewText = new ViewText( viewDocument, 'foo' );
const viewP = new ViewElement( viewDocument, 'p', { class: 'foo' } );

viewP._appendChild( viewStyle );
viewP._appendChild( viewText );

converter = new DomConverter( viewDocument, {
renderingMode: 'editing'
} );

converter.viewToDom( viewP, document );

sinon.assert.calledOnce( warnStub );
sinon.assert.calledWithExactly( warnStub,
sinon.match( /^domconverter-unsafe-style-element-detected/ ),
sinon.match.string // Link to the documentation
);
} );
Expand Down
179 changes: 179 additions & 0 deletions packages/ckeditor5-html-support/tests/integrations/style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* @license Copyright (c) 2003-2022, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import GeneralHtmlSupport from '../../src/generalhtmlsupport';
import { getModelDataWithAttributes } from '../_utils/utils';
import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';

/* global console, document */

describe( 'StyleElementSupport', () => {
const STYLE = 'div { color: red; }';
const CODE_CPP = 'cout << "Hello World" << endl;';

let editor, model, editorElement, dataFilter, warnStub;

beforeEach( async () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );

editor = await ClassicTestEditor.create( editorElement, {
plugins: [ Paragraph, GeneralHtmlSupport ]
} );
model = editor.model;
dataFilter = editor.plugins.get( 'DataFilter' );

dataFilter.allowElement( 'style' );

warnStub = sinon.stub( console, 'warn' );
} );

afterEach( () => {
warnStub.restore();
editorElement.remove();

return editor.destroy();
} );

it( 'should allow element', () => {
editor.setData( `<p>Foo</p><style>${ STYLE }</style>` );

expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
`<paragraph>Foo</paragraph><htmlStyle htmlContent="${ STYLE }"></htmlStyle>`
);

expect( editor.getData() ).to.equal( `<p>Foo</p><style>${ STYLE }</style>` );
} );

it( 'should allow attributes', () => {
dataFilter.allowAttributes( { name: 'style', attributes: [ 'type', 'nonce' ] } );

editor.setData( `<p>Foo</p><style type="c++" nonce="qwerty">${ CODE_CPP }</style>` );

expect( getModelDataWithAttributes( model, { withoutSelection: true } ) ).to.deep.equal( {
data: `<paragraph>Foo</paragraph><htmlStyle htmlAttributes="(1)" htmlContent="${ CODE_CPP }"></htmlStyle>`,
attributes: {
1: {
attributes: {
nonce: 'qwerty',
type: 'c++'
}
},
2: CODE_CPP
}
} );

expect( editor.getData() ).to.equal( `<p>Foo</p><style type="c++" nonce="qwerty">${ CODE_CPP }</style>` );
} );

it( 'should disallow attributes', () => {
dataFilter.allowAttributes( { name: 'style', attributes: true } );
dataFilter.disallowAttributes( { name: 'style', attributes: 'nonce' } );

editor.setData( `<p>Foo</p><style type="c++" nonce="qwerty">${ CODE_CPP }</style>` );

expect( getModelDataWithAttributes( model, { withoutSelection: true } ) ).to.deep.equal( {
data: `<paragraph>Foo</paragraph><htmlStyle htmlAttributes="(1)" htmlContent="${ CODE_CPP }"></htmlStyle>`,
attributes: {
1: {
attributes: {
type: 'c++'
}
},
2: CODE_CPP
}
} );

expect( editor.getData() ).to.equal( `<p>Foo</p><style type="c++">${ CODE_CPP }</style>` );
} );

describe( 'element position', () => {
const testCases = [ {
name: 'paragraph',
data: `<article><section><p>Foo<style>${ STYLE }</style>Bar</p></section></article>`,
model:
'<htmlArticle>' +
`<htmlSection><paragraph>Foo<htmlStyle htmlContent="${ STYLE }"></htmlStyle>Bar</paragraph></htmlSection>` +
'</htmlArticle>'
}, {
name: 'section',
data: `<article><section><p>Foo</p><style>${ STYLE }</style></section></article>`,
model:
'<htmlArticle>' +
`<htmlSection><paragraph>Foo</paragraph><htmlStyle htmlContent="${ STYLE }"></htmlStyle></htmlSection>` +
'</htmlArticle>'
}, {
name: 'article',
data: `<article><section><p>Foo</p></section><style>${ STYLE }</style></article>`,
model:
'<htmlArticle>' +
`<htmlSection><paragraph>Foo</paragraph></htmlSection><htmlStyle htmlContent="${ STYLE }"></htmlStyle>` +
'</htmlArticle>'
}, {
name: 'root',
data: `<article><section><p>Foo</p></section></article><style>${ STYLE }</style>`,
model:
'<htmlArticle><htmlSection><paragraph>Foo</paragraph></htmlSection></htmlArticle>' +
`<htmlStyle htmlContent="${ STYLE }"></htmlStyle>`
} ];

for ( const { name, data, model: modelData } of testCases ) {
it( `should allow element inside ${ name }`, () => {
dataFilter.allowElement( 'article' );
dataFilter.allowElement( 'section' );

editor.setData( data );

expect( getModelData( model, { withoutSelection: true } ) ).to.equal( modelData );

expect( editor.getData() ).to.equal( data );
} );
}
} );

it( 'should not consume attributes already consumed (downcast)', () => {
dataFilter.allowAttributes( { name: 'style', attributes: true } );

editor.conversion.for( 'downcast' ).add( dispatcher => {
dispatcher.on( 'attribute:htmlAttributes:htmlStyle', ( evt, data, conversionApi ) => {
conversionApi.consumable.consume( data.item, evt.name );
}, { priority: 'high' } );
} );

editor.setData( `<p>Foo</p><style nonce="qwerty">${ STYLE }</style>` );

expect( getModelDataWithAttributes( model, { withoutSelection: true } ) ).to.deep.equal( {
data: `<paragraph>Foo</paragraph><htmlStyle htmlAttributes="(1)" htmlContent="${ STYLE }"></htmlStyle>`,
attributes: {
1: { attributes: { nonce: 'qwerty' } },
2: STYLE
}
} );

expect( editor.getData() ).to.equal( `<p>Foo</p><style>${ STYLE }</style>` );
} );

it( 'should not consume attributes already consumed (upcast)', () => {
dataFilter.allowAttributes( { name: 'style', attributes: true } );

editor.conversion.for( 'upcast' ).add( dispatcher => {
dispatcher.on( 'element:style', ( evt, data, conversionApi ) => {
conversionApi.consumable.consume( data.viewItem, { attributes: 'nonce' } );
}, { priority: 'high' } );
} );

editor.setData( `<p>Foo</p><style type="text/css" nonce="qwerty">${ STYLE }</style>` );

expect( getModelDataWithAttributes( model, { withoutSelection: true } ) ).to.deep.equal( {
data: `<paragraph>Foo</paragraph><htmlStyle htmlAttributes="(1)" htmlContent="${ STYLE }"></htmlStyle>`,
attributes: {
1: { attributes: { type: 'text/css' } },
2: STYLE
}
} );
} );
} );

0 comments on commit 003e537

Please sign in to comment.