From 7a296c4cff8ff1eca4d8e05d1f49ebf235566599 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Wed, 10 Jul 2019 16:53:58 +0200 Subject: [PATCH 01/48] Add function to determines the source app of the data input. --- src/pastefromoffice.js | 58 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index ff92d3f..8936a3d 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -8,6 +8,7 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; +import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; import { parseHtml } from './filters/parse'; import { transformListItemLikeElementsIntoLists } from './filters/list'; @@ -40,8 +41,17 @@ export default class PasteFromOffice extends Plugin { this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', ( evt, data ) => { const html = data.dataTransfer.getData( 'text/html' ); - if ( data.pasteFromOfficeProcessed !== true && isWordInput( html ) ) { - data.content = this._normalizeWordInput( html, data.dataTransfer ); + if ( data.pasteFromOfficeProcessed !== true ) { + switch ( getInputType( html ) ) { + case 'msword': + data.content = this._normalizeWordInput( html, data.dataTransfer ); + break; + case 'gdocs': + data.content = this._normalizeGDocsInput( data.content ); + break; + default: + break; + } // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). data.pasteFromOfficeProcessed = true; @@ -67,13 +77,45 @@ export default class PasteFromOffice extends Plugin { return body; } + + _normalizeGDocsInput( documentFragment ) { + // there is global wrapper with element + return removeBoldTagWrapper( documentFragment ); + } } -// Checks if given HTML string is a result of pasting content from Word. +function removeBoldTagWrapper( documentFragment ) { + if ( documentFragment.childCount === 1 ) { + const firstChild = documentFragment.getChild( 0 ); + + if ( firstChild.name === 'b' && firstChild.getStyle( 'font-weight' ) === 'normal' ) { + return new DocumentFragment( firstChild.getChildren() ); + } + } + + return documentFragment; +} + +// Determines if given paste data came from specific office-like application +// Recognized are: +// * 'msword' for Microsoft Words desktop app +// * 'gdocs' for Google Docs online app // -// @param {String} html HTML string to test. -// @returns {Boolean} True if given HTML string is a Word HTML. -function isWordInput( html ) { - return !!( html && ( html.match( //gi ) || - html.match( /xmlns:o="urn:schemas-microsoft-com/gi ) ) ); +// @param {String} html `text/html` string from data transfer +// @return {String|null} type of app which is source of a data or null +function getInputType( html ) { + if ( html ) { + if ( + //i.test( html ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( html ) + ) { + // Microsoft Word detection + return 'msword'; + } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { + // Google Docs detection + return 'gdocs'; + } + } + + return null; } From 53152ab2cdcb05a3835fa656fa2c5ed5ad4f3001 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 15 Jul 2019 12:34:34 +0200 Subject: [PATCH 02/48] Add test for google docs, fix unit test creator to fully simulate clipboard event. Extend unit test with information about data source. Some small docs and test naming improvements. --- src/pastefromoffice.js | 55 +++++++++++-------- tests/_data/paste-from-google-docs/index.js | 24 ++++++++ .../simple-text/input.html | 1 + .../simple-text/model.html | 1 + .../simple-text/normalized.html | 1 + .../simple-text/simple-text.html | 1 + tests/_utils/fixtures.js | 7 ++- tests/_utils/utils.js | 43 +++++++++++---- tests/data/integration.js | 17 +++++- tests/data/normalization.js | 15 ++++- tests/manual/integration.html | 11 +++- 11 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 tests/_data/paste-from-google-docs/index.js create mode 100644 tests/_data/paste-from-google-docs/simple-text/input.html create mode 100644 tests/_data/paste-from-google-docs/simple-text/model.html create mode 100644 tests/_data/paste-from-google-docs/simple-text/normalized.html create mode 100644 tests/_data/paste-from-google-docs/simple-text/simple-text.html diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 8936a3d..d701f30 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -8,7 +8,6 @@ */ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; import { parseHtml } from './filters/parse'; import { transformListItemLikeElementsIntoLists } from './filters/list'; @@ -38,25 +37,32 @@ export default class PasteFromOffice extends Plugin { init() { const editor = this.editor; - this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', ( evt, data ) => { - const html = data.dataTransfer.getData( 'text/html' ); - - if ( data.pasteFromOfficeProcessed !== true ) { - switch ( getInputType( html ) ) { - case 'msword': - data.content = this._normalizeWordInput( html, data.dataTransfer ); - break; - case 'gdocs': - data.content = this._normalizeGDocsInput( data.content ); - break; - default: - break; - } - - // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). - data.pasteFromOfficeProcessed = true; + this.listenTo( + editor.plugins.get( 'Clipboard' ), + 'inputTransformation', + this._inputTransformationListener.bind( this ), + { priority: 'high' } + ); + } + + _inputTransformationListener( evt, data ) { + const html = data.dataTransfer.getData( 'text/html' ); + + if ( data.pasteFromOfficeProcessed !== true ) { + switch ( getInputType( html ) ) { + case 'msword': + data.content = this._normalizeWordInput( html, data.dataTransfer ); + break; + case 'gdocs': + data.content = this._normalizeGDocsInput( data.content ); + break; + default: + break; } - }, { priority: 'high' } ); + + // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). + data.pasteFromOfficeProcessed = true; + } } /** @@ -85,12 +91,13 @@ export default class PasteFromOffice extends Plugin { } function removeBoldTagWrapper( documentFragment ) { - if ( documentFragment.childCount === 1 ) { - const firstChild = documentFragment.getChild( 0 ); + const firstChild = documentFragment.getChild( 0 ); - if ( firstChild.name === 'b' && firstChild.getStyle( 'font-weight' ) === 'normal' ) { - return new DocumentFragment( firstChild.getChildren() ); - } + if ( firstChild.name === 'b' && firstChild.getStyle( 'font-weight' ) === 'normal' ) { + const children = firstChild.getChildren(); + + documentFragment._removeChildren( 0 ); + documentFragment._insertChild( 0, children ); } return documentFragment; diff --git a/tests/_data/paste-from-google-docs/index.js b/tests/_data/paste-from-google-docs/index.js new file mode 100644 index 0000000..6c42bd9 --- /dev/null +++ b/tests/_data/paste-from-google-docs/index.js @@ -0,0 +1,24 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import simpleText from './simple-text/input.html'; + +import simpleTextNormalized from './simple-text/normalized.html'; + +import simpleTextModel from './simple-text/model.html'; + +export const fixtures = { + input: { + simpleText + }, + normalized: { + simpleText: simpleTextNormalized + }, + model: { + simpleText: simpleTextModel + } +}; + +export const browserFixtures = {}; diff --git a/tests/_data/paste-from-google-docs/simple-text/input.html b/tests/_data/paste-from-google-docs/simple-text/input.html new file mode 100644 index 0000000..7027a14 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text/input.html @@ -0,0 +1 @@ +

Hello world


diff --git a/tests/_data/paste-from-google-docs/simple-text/model.html b/tests/_data/paste-from-google-docs/simple-text/model.html new file mode 100644 index 0000000..5d87fd8 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text/model.html @@ -0,0 +1 @@ +Hello world diff --git a/tests/_data/paste-from-google-docs/simple-text/normalized.html b/tests/_data/paste-from-google-docs/simple-text/normalized.html new file mode 100644 index 0000000..fb099a7 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text/normalized.html @@ -0,0 +1 @@ +

Hello world


diff --git a/tests/_data/paste-from-google-docs/simple-text/simple-text.html b/tests/_data/paste-from-google-docs/simple-text/simple-text.html new file mode 100644 index 0000000..7459856 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text/simple-text.html @@ -0,0 +1 @@ +

Hello world

diff --git a/tests/_utils/fixtures.js b/tests/_utils/fixtures.js index efca05d..d72569d 100644 --- a/tests/_utils/fixtures.js +++ b/tests/_utils/fixtures.js @@ -9,6 +9,7 @@ import { fixtures as image, browserFixtures as imageBrowser } from '../_data/ima import { fixtures as link, browserFixtures as linkBrowser } from '../_data/link/index.js'; import { fixtures as list, browserFixtures as listBrowser } from '../_data/list/index.js'; import { fixtures as spacing, browserFixtures as spacingBrowser } from '../_data/spacing/index.js'; +import { fixtures as simpleText, browserFixtures as simpleTextBrowser } from '../_data/paste-from-google-docs/index'; // Generic fixtures. export const fixtures = { @@ -16,7 +17,8 @@ export const fixtures = { image, link, list, - spacing + spacing, + simpleText }; // Browser specific fixtures. @@ -25,5 +27,6 @@ export const browserFixtures = { image: imageBrowser, link: linkBrowser, list: listBrowser, - spacing: spacingBrowser + spacing: spacingBrowser, + simpleTextBrowser }; diff --git a/tests/_utils/utils.js b/tests/_utils/utils.js index 81a1614..b32e5dd 100644 --- a/tests/_utils/utils.js +++ b/tests/_utils/utils.js @@ -8,6 +8,9 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import normalizeClipboardData from '@ckeditor/ckeditor5-clipboard/src/utils/normalizeclipboarddata'; + import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml'; import { setData, stringify as stringifyModel } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; @@ -42,6 +45,7 @@ export function createDataTransfer( data ) { * @param {Object} config * @param {String} config.type Type of tests to generate, could be 'normalization' or 'integration'. * @param {String} config.input Name of the fixtures group. Usually stored in `/tests/_data/groupname/`. + * @param {String} config.dataSource Name of the office app, which was used to generate data. * @param {Array.} config.browsers List of all browsers for which to generate tests. * @param {Object} [config.editorConfig] Editor config which is passed to editor `create()` method. * @param {Object} [config.skip] List of fixtures for any browser to skip. The supported format is: @@ -55,6 +59,10 @@ export function generateTests( config ) { throw new Error( `Invalid tests type - \`config.type\`: '${ config.type }'.` ); } + if ( !config.dataSource ) { + throw new Error( 'No `config.dataSource` option provided.' ); + } + if ( !config.input ) { throw new Error( 'No `config.input` option provided.' ); } @@ -67,16 +75,18 @@ export function generateTests( config ) { const generateSuiteFn = config.type === 'normalization' ? generateNormalizationTests : generateIntegrationTests; describe( config.type, () => { - describe( config.input, () => { - const editorConfig = config.editorConfig || {}; + describe( config.dataSource, () => { + describe( config.input, () => { + const editorConfig = config.editorConfig || {}; - for ( const group of Object.keys( groups ) ) { - const skip = config.skip && config.skip[ group ] ? config.skip[ group ] : []; + for ( const group of Object.keys( groups ) ) { + const skip = config.skip && config.skip[ group ] ? config.skip[ group ] : []; - if ( groups[ group ] ) { - generateSuiteFn( group, groups[ group ], editorConfig, skip ); + if ( groups[ group ] ) { + generateSuiteFn( group, groups[ group ], editorConfig, skip ); + } } - } + } ); } ); } ); } @@ -124,12 +134,16 @@ function groupFixturesByBrowsers( browsers, fixturesGroup, skipBrowsers ) { } // Generates normalization tests based on a provided fixtures. For each input fixture one test is generated. +// Please notice that normalization compares generated Views, not DOM. That's why there might appear some not familiar structures, +// like closing tags for void tags, for example `

`. // // @param {String} title Tests group title. // @param {Object} fixtures Object containing fixtures. // @param {Object} editorConfig Editor config with which test editor will be created. // @param {Array.} skip Array of fixtures names which tests should be skipped. function generateNormalizationTests( title, fixtures, editorConfig, skip ) { + const htmlDataProcessor = new HtmlDataProcessor(); + describe( title, () => { let editor, pasteFromOfficePlugin; @@ -151,12 +165,19 @@ function generateNormalizationTests( title, fixtures, editorConfig, skip ) { for ( const name of Object.keys( fixtures.input ) ) { ( skip.indexOf( name ) !== -1 ? it.skip : it )( name, () => { - const dataTransfer = createDataTransfer( { - 'text/rtf': fixtures.inputRtf && fixtures.inputRtf[ name ] - } ); + // Simulate data from Clipboard event + const data = { + content: htmlDataProcessor.toView( normalizeClipboardData( fixtures.input[ name ] ) ), + dataTransfer: createDataTransfer( { + 'text/html': fixtures.input[ name ], + 'text/rtf': fixtures.inputRtf && fixtures.inputRtf[ name ] + } ) + }; + + pasteFromOfficePlugin._inputTransformationListener( null, data ); expectNormalized( - pasteFromOfficePlugin._normalizeWordInput( fixtures.input[ name ], dataTransfer ), + data.content, fixtures.normalized[ name ] ); } ); diff --git a/tests/data/integration.js b/tests/data/integration.js index 2145846..25f25b6 100644 --- a/tests/data/integration.js +++ b/tests/data/integration.js @@ -22,10 +22,11 @@ import { generateTests } from '../_utils/utils'; const browsers = [ 'chrome', 'firefox', 'safari', 'edge' ]; -describe( 'Paste from Office', () => { +describe( 'Paste from Office - automatic', () => { generateTests( { input: 'basic-styles', type: 'integration', + dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Heading, Bold, Italic, Underline, Strikethrough, PasteFromOffice ] @@ -38,6 +39,7 @@ describe( 'Paste from Office', () => { generateTests( { input: 'image', type: 'integration', + dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Image, Table, PasteFromOffice ] @@ -53,6 +55,7 @@ describe( 'Paste from Office', () => { generateTests( { input: 'link', type: 'integration', + dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Heading, Bold, Link, ShiftEnter, PasteFromOffice ] @@ -65,6 +68,7 @@ describe( 'Paste from Office', () => { generateTests( { input: 'list', type: 'integration', + dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Heading, Bold, Italic, Underline, Link, List, PasteFromOffice ] @@ -77,9 +81,20 @@ describe( 'Paste from Office', () => { generateTests( { input: 'spacing', type: 'integration', + dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Bold, Italic, Underline, PasteFromOffice ] } } ); + + generateTests( { + input: 'simpleText', + type: 'integration', + dataSource: 'Google Docs', + browsers, + editorConfig: { + plugins: [ Clipboard, Paragraph, Bold, PasteFromOffice ] + } + } ); } ); diff --git a/tests/data/normalization.js b/tests/data/normalization.js index c8452c2..0840a7d 100644 --- a/tests/data/normalization.js +++ b/tests/data/normalization.js @@ -14,10 +14,11 @@ const editorConfig = { plugins: [ Clipboard, PasteFromOffice ] }; -describe( 'Paste from Office', () => { +describe( 'Paste from Office - automatic', () => { generateTests( { input: 'basic-styles', type: 'normalization', + dataSource: 'MS Word', browsers, editorConfig } ); @@ -25,6 +26,7 @@ describe( 'Paste from Office', () => { generateTests( { input: 'image', type: 'normalization', + dataSource: 'MS Word', browsers, editorConfig } ); @@ -32,6 +34,7 @@ describe( 'Paste from Office', () => { generateTests( { input: 'link', type: 'normalization', + dataSource: 'MS Word', browsers, editorConfig } ); @@ -39,6 +42,7 @@ describe( 'Paste from Office', () => { generateTests( { input: 'list', type: 'normalization', + dataSource: 'MS Word', browsers, editorConfig } ); @@ -46,6 +50,15 @@ describe( 'Paste from Office', () => { generateTests( { input: 'spacing', type: 'normalization', + dataSource: 'MS Word', + browsers, + editorConfig + } ); + + generateTests( { + input: 'simpleText', + type: 'normalization', + dataSource: 'Google Docs', browsers, editorConfig } ); diff --git a/tests/manual/integration.html b/tests/manual/integration.html index 15955a7..09375e5 100644 --- a/tests/manual/integration.html +++ b/tests/manual/integration.html @@ -1,3 +1,8 @@ +

Paste here:

@@ -10,11 +15,11 @@

Paste here:

-Clipboard HTML +

Clipboard HTML

-Clipboard text +

Clipboard text

-Clipboard HTML after transformation +

Clipboard normalized HTML view after transformation

From ea75c238cbf0951691d4e2ae7062a23597e5aea9 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 15 Jul 2019 15:25:36 +0200 Subject: [PATCH 03/48] Extrct part of the logic to separate files. --- src/filters/common.js | 27 +++++++++++ src/pastefromoffice.js | 87 +---------------------------------- src/utils.js | 100 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 85 deletions(-) create mode 100644 src/filters/common.js create mode 100644 src/utils.js diff --git a/src/filters/common.js b/src/filters/common.js new file mode 100644 index 0000000..3d13fdd --- /dev/null +++ b/src/filters/common.js @@ -0,0 +1,27 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/filters/common + */ + +/** + * Removes tag wrapper added by Google Docs for copied content. + * + * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment + * @returns {module:engine/view/documentfragment~DocumentFragment} + */ +export function removeBoldTagWrapper( documentFragment ) { + const firstChild = documentFragment.getChild( 0 ); + + if ( firstChild.name === 'b' && firstChild.getStyle( 'font-weight' ) === 'normal' ) { + const children = firstChild.getChildren(); + + documentFragment._removeChildren( 0 ); + documentFragment._insertChild( 0, children ); + } + + return documentFragment; +} diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index d701f30..f932d04 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,9 +9,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import { parseHtml } from './filters/parse'; -import { transformListItemLikeElementsIntoLists } from './filters/list'; -import { replaceImagesSourceWithBase64 } from './filters/image'; +import { _inputTransformationListener } from './utils'; /** * The Paste from Office plugin. @@ -40,89 +38,8 @@ export default class PasteFromOffice extends Plugin { this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', - this._inputTransformationListener.bind( this ), + _inputTransformationListener, { priority: 'high' } ); } - - _inputTransformationListener( evt, data ) { - const html = data.dataTransfer.getData( 'text/html' ); - - if ( data.pasteFromOfficeProcessed !== true ) { - switch ( getInputType( html ) ) { - case 'msword': - data.content = this._normalizeWordInput( html, data.dataTransfer ); - break; - case 'gdocs': - data.content = this._normalizeGDocsInput( data.content ); - break; - default: - break; - } - - // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). - data.pasteFromOfficeProcessed = true; - } - } - - /** - * Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. - * - * **Note**: this function was exposed mainly for testing purposes and should not be called directly. - * - * @protected - * @param {String} input Word input. - * @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. - * @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. - */ - _normalizeWordInput( input, dataTransfer ) { - const { body, stylesString } = parseHtml( input ); - - transformListItemLikeElementsIntoLists( body, stylesString ); - replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); - - return body; - } - - _normalizeGDocsInput( documentFragment ) { - // there is global wrapper with element - return removeBoldTagWrapper( documentFragment ); - } -} - -function removeBoldTagWrapper( documentFragment ) { - const firstChild = documentFragment.getChild( 0 ); - - if ( firstChild.name === 'b' && firstChild.getStyle( 'font-weight' ) === 'normal' ) { - const children = firstChild.getChildren(); - - documentFragment._removeChildren( 0 ); - documentFragment._insertChild( 0, children ); - } - - return documentFragment; -} - -// Determines if given paste data came from specific office-like application -// Recognized are: -// * 'msword' for Microsoft Words desktop app -// * 'gdocs' for Google Docs online app -// -// @param {String} html `text/html` string from data transfer -// @return {String|null} type of app which is source of a data or null -function getInputType( html ) { - if ( html ) { - if ( - //i.test( html ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( html ) - ) { - // Microsoft Word detection - return 'msword'; - } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { - // Google Docs detection - return 'gdocs'; - } - } - - return null; } diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..2733908 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,100 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/pastefromoffice/utils + */ + +import { parseHtml } from './filters/parse'; +import { transformListItemLikeElementsIntoLists } from './filters/list'; +import { replaceImagesSourceWithBase64 } from './filters/image'; +import { removeBoldTagWrapper } from './filters/common'; + +/** + * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} event. Detects if content comes + * from recognized source and normalize it. + * + * **Note**: this function was exposed mainly for testing purposes and should not be called directly. + * + * @param {module:utils/eventinfo~EventInfo} evt + * @param {Object} data passed with {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} + */ +export function _inputTransformationListener( evt, data ) { + const html = data.dataTransfer.getData( 'text/html' ); + + if ( data.pasteFromOfficeProcessed !== true ) { + switch ( getInputType( html ) ) { + case 'msword': + data.content = _normalizeWordInput( html, data.dataTransfer ); + break; + case 'gdocs': + data.content = _normalizeGDocsInput( data.content ); + break; + default: + break; + } + + // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). + data.pasteFromOfficeProcessed = true; + } +} + +/** + * Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. + * + * **Note**: this function is exposed mainly for testing purposes and should not be called directly. + * + * @private + * @param {String} input Word input. + * @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. + * @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. + */ +export function _normalizeWordInput( input, dataTransfer ) { + const { body, stylesString } = parseHtml( input ); + + transformListItemLikeElementsIntoLists( body, stylesString ); + replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); + + return body; +} + +/** + * Normalizes input pasted from Google Docs to format suitable for editor {@link module:engine/model/model~Model}. + * + * **Note**: this function is exposed mainly for testing purposes and should not be called directly. + * + * @private + * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment normalized clipboard data + * @returns {module:engine/view/documentfragment~DocumentFragment} document fragment normalized with set of filters dedicated + * for Google Docs + */ +export function _normalizeGDocsInput( documentFragment ) { + return removeBoldTagWrapper( documentFragment ); +} + +/** Determines if given paste data came from specific office-like application + * Recognized are: + * * 'msword' for Microsoft Words desktop app + * * 'gdocs' for Google Docs online app + * + * @param {String} html `text/html` string from data transfer + * @return {String|null} type of app which is source of a data or null + */ +export function getInputType( html ) { + if ( html ) { + if ( + //i.test( html ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( html ) + ) { + // Microsoft Word detection + return 'msword'; + } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { + // Google Docs detection + return 'gdocs'; + } + } + + return null; +} From 4e214589f5c34908df9525c46df5f332f5d10839 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 15 Jul 2019 16:26:37 +0200 Subject: [PATCH 04/48] Move utils to plugin to provide better way of testing it. --- src/pastefromoffice.js | 97 ++++++++++++++++++++++++++++++++++++- src/utils.js | 100 --------------------------------------- tests/_utils/utils.js | 10 ++-- tests/pastefromoffice.js | 98 +++++++++++++++++++------------------- 4 files changed, 148 insertions(+), 157 deletions(-) delete mode 100644 src/utils.js diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index f932d04..b8affa0 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,7 +9,10 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import { _inputTransformationListener } from './utils'; +import { parseHtml } from './filters/parse'; +import { transformListItemLikeElementsIntoLists } from './filters/list'; +import { replaceImagesSourceWithBase64 } from './filters/image'; +import { removeBoldTagWrapper } from './filters/common'; /** * The Paste from Office plugin. @@ -38,8 +41,98 @@ export default class PasteFromOffice extends Plugin { this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', - _inputTransformationListener, + PasteFromOffice._inputTransformationListener, { priority: 'high' } ); } + + /** + * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} event. Detects if content comes + * from recognized source and normalize it. + * + * **Note**: this function was exposed mainly for testing purposes and should not be called directly. + * + * @private + * @param {module:utils/eventinfo~EventInfo} evt + * @param {Object} data passed with {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} + */ + static _inputTransformationListener( evt, data ) { + const html = data.dataTransfer.getData( 'text/html' ); + + if ( data.pasteFromOfficeProcessed !== true ) { + switch ( PasteFromOffice._getInputType( html ) ) { + case 'msword': + data.content = PasteFromOffice._normalizeWordInput( html, data.dataTransfer ); + break; + case 'gdocs': + data.content = PasteFromOffice._normalizeGoogleDocsInput( data.content ); + break; + default: + break; + } + + // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). + data.pasteFromOfficeProcessed = true; + } + } + + /** + * Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. + * + * **Note**: this function is exposed mainly for testing purposes and should not be called directly. + * + * @private + * @param {String} input Word input. + * @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. + * @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. + */ + static _normalizeWordInput( input, dataTransfer ) { + const { body, stylesString } = parseHtml( input ); + + transformListItemLikeElementsIntoLists( body, stylesString ); + replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); + + return body; + } + + /** + * Normalizes input pasted from Google Docs to format suitable for editor {@link module:engine/model/model~Model}. + * + * **Note**: this function is exposed mainly for testing purposes and should not be called directly. + * + * @private + * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment normalized clipboard data + * @returns {module:engine/view/documentfragment~DocumentFragment} document fragment normalized with set of filters dedicated + * for Google Docs + */ + static _normalizeGoogleDocsInput( documentFragment ) { + return removeBoldTagWrapper( documentFragment ); + } + + /** + * Determines if given paste data came from specific office-like application + * Recognized are: + * * 'msword' for Microsoft Words desktop app + * * 'gdocs' for Google Docs online app + * + * @private + * @param {String} html `text/html` string from data transfer + * @return {String|null} type of app which is source of a data or null + */ + static _getInputType( html ) { + if ( html ) { + if ( + //i.test( html ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( html ) + ) { + // Microsoft Word detection + return 'msword'; + } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { + // Google Docs detection + return 'gdocs'; + } + } + + return null; + } } diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 2733908..0000000 --- a/src/utils.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module paste-from-office/pastefromoffice/utils - */ - -import { parseHtml } from './filters/parse'; -import { transformListItemLikeElementsIntoLists } from './filters/list'; -import { replaceImagesSourceWithBase64 } from './filters/image'; -import { removeBoldTagWrapper } from './filters/common'; - -/** - * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} event. Detects if content comes - * from recognized source and normalize it. - * - * **Note**: this function was exposed mainly for testing purposes and should not be called directly. - * - * @param {module:utils/eventinfo~EventInfo} evt - * @param {Object} data passed with {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} - */ -export function _inputTransformationListener( evt, data ) { - const html = data.dataTransfer.getData( 'text/html' ); - - if ( data.pasteFromOfficeProcessed !== true ) { - switch ( getInputType( html ) ) { - case 'msword': - data.content = _normalizeWordInput( html, data.dataTransfer ); - break; - case 'gdocs': - data.content = _normalizeGDocsInput( data.content ); - break; - default: - break; - } - - // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). - data.pasteFromOfficeProcessed = true; - } -} - -/** - * Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. - * - * **Note**: this function is exposed mainly for testing purposes and should not be called directly. - * - * @private - * @param {String} input Word input. - * @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. - * @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. - */ -export function _normalizeWordInput( input, dataTransfer ) { - const { body, stylesString } = parseHtml( input ); - - transformListItemLikeElementsIntoLists( body, stylesString ); - replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); - - return body; -} - -/** - * Normalizes input pasted from Google Docs to format suitable for editor {@link module:engine/model/model~Model}. - * - * **Note**: this function is exposed mainly for testing purposes and should not be called directly. - * - * @private - * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment normalized clipboard data - * @returns {module:engine/view/documentfragment~DocumentFragment} document fragment normalized with set of filters dedicated - * for Google Docs - */ -export function _normalizeGDocsInput( documentFragment ) { - return removeBoldTagWrapper( documentFragment ); -} - -/** Determines if given paste data came from specific office-like application - * Recognized are: - * * 'msword' for Microsoft Words desktop app - * * 'gdocs' for Google Docs online app - * - * @param {String} html `text/html` string from data transfer - * @return {String|null} type of app which is source of a data or null - */ -export function getInputType( html ) { - if ( html ) { - if ( - //i.test( html ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( html ) - ) { - // Microsoft Word detection - return 'msword'; - } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { - // Google Docs detection - return 'gdocs'; - } - } - - return null; -} diff --git a/tests/_utils/utils.js b/tests/_utils/utils.js index b32e5dd..2f9a331 100644 --- a/tests/_utils/utils.js +++ b/tests/_utils/utils.js @@ -8,9 +8,9 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; +import PasteFromOffice from '../../src/pastefromoffice'; import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; import normalizeClipboardData from '@ckeditor/ckeditor5-clipboard/src/utils/normalizeclipboarddata'; - import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml'; import { setData, stringify as stringifyModel } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import { stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view'; @@ -145,22 +145,18 @@ function generateNormalizationTests( title, fixtures, editorConfig, skip ) { const htmlDataProcessor = new HtmlDataProcessor(); describe( title, () => { - let editor, pasteFromOfficePlugin; + let editor; beforeEach( () => { return VirtualTestEditor .create( editorConfig ) .then( newEditor => { editor = newEditor; - - pasteFromOfficePlugin = editor.plugins.get( 'PasteFromOffice' ); } ); } ); afterEach( () => { editor.destroy(); - - pasteFromOfficePlugin = null; } ); for ( const name of Object.keys( fixtures.input ) ) { @@ -174,7 +170,7 @@ function generateNormalizationTests( title, fixtures, editorConfig, skip ) { } ) }; - pasteFromOfficePlugin._inputTransformationListener( null, data ); + PasteFromOffice._inputTransformationListener( null, data ); expectNormalized( data.content, diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index a2692e5..de5fee6 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -16,74 +16,76 @@ describe( 'Paste from Office plugin', () => { testUtils.createSinonSandbox(); - before( () => { - content = new DocumentFragment(); - } ); + describe( '_normalizeWordInput()', () => { + before( () => { + content = new DocumentFragment(); + } ); - beforeEach( () => { - return VirtualTestEditor - .create( { - plugins: [ Clipboard, PasteFromOffice ] - } ) - .then( newEditor => { - editor = newEditor; - normalizeSpy = testUtils.sinon.spy( editor.plugins.get( 'PasteFromOffice' ), '_normalizeWordInput' ); + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ Clipboard, PasteFromOffice ] + } ) + .then( newEditor => { + editor = newEditor; + normalizeSpy = testUtils.sinon.spy( PasteFromOffice, '_normalizeWordInput' ); + } ); + } ); + + it( 'runs normalizations if Word meta tag detected #1', () => { + const dataTransfer = createDataTransfer( { + 'text/html': '' } ); - } ); - it( 'runs normalizations if Word meta tag detected #1', () => { - const dataTransfer = createDataTransfer( { - 'text/html': '' + editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); + + expect( normalizeSpy.calledOnce ).to.true; } ); - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); + it( 'runs normalizations if Word meta tag detected #2', () => { + const dataTransfer = createDataTransfer( { + 'text/html': '' + } ); - expect( normalizeSpy.calledOnce ).to.true; - } ); + editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - it( 'runs normalizations if Word meta tag detected #2', () => { - const dataTransfer = createDataTransfer( { - 'text/html': '' + expect( normalizeSpy.calledOnce ).to.true; } ); - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); + it( 'does not normalize the content without Word meta tag', () => { + const dataTransfer = createDataTransfer( { + 'text/html': '' + } ); - expect( normalizeSpy.calledOnce ).to.true; - } ); + editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - it( 'does not normalize the content without Word meta tag', () => { - const dataTransfer = createDataTransfer( { - 'text/html': '' + expect( normalizeSpy.called ).to.false; } ); - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); + it( 'does not process content many times for the same `inputTransformation` event', () => { + const clipboard = editor.plugins.get( 'Clipboard' ); - expect( normalizeSpy.called ).to.false; - } ); + const dataTransfer = createDataTransfer( { + 'text/html': '' + } ); - it( 'does not process content many times for the same `inputTransformation` event', () => { - const clipboard = editor.plugins.get( 'Clipboard' ); + let eventRefired = false; + clipboard.on( 'inputTransformation', ( evt, data ) => { + if ( !eventRefired ) { + eventRefired = true; - const dataTransfer = createDataTransfer( { - 'text/html': '' - } ); + evt.stop(); - let eventRefired = false; - clipboard.on( 'inputTransformation', ( evt, data ) => { - if ( !eventRefired ) { - eventRefired = true; + clipboard.fire( 'inputTransformation', data ); + } - evt.stop(); + expect( data.pasteFromOfficeProcessed ).to.true; + expect( normalizeSpy.calledOnce ).to.true; + }, { priority: 'low' } ); - clipboard.fire( 'inputTransformation', data ); - } + editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - expect( data.pasteFromOfficeProcessed ).to.true; expect( normalizeSpy.calledOnce ).to.true; - }, { priority: 'low' } ); - - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - - expect( normalizeSpy.calledOnce ).to.true; + } ); } ); } ); From 60a5e53a7f275ea2603e483cbf7397c325927304 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 16 Jul 2019 10:05:52 +0200 Subject: [PATCH 05/48] Add unit test from Firefox source, rename test case, simplify getInputType method, remove dataSource from autoamtic tests which was confusing. --- src/pastefromoffice.js | 20 +++--- tests/_data/paste-from-google-docs/index.js | 12 +++- .../simple-text-from-firefox/input.html | 1 + .../simple-text-from-firefox/model.html | 1 + .../simple-text-from-firefox/normalized.html | 1 + .../simple-text-from-firefox.html | 1 + tests/_utils/fixtures.js | 6 +- tests/_utils/utils.js | 21 ++---- tests/data/integration.js | 8 +-- tests/data/normalization.js | 8 +-- tests/pastefromoffice.js | 70 +++++++++++++++++++ 11 files changed, 104 insertions(+), 45 deletions(-) create mode 100644 tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html create mode 100644 tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html create mode 100644 tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html create mode 100755 tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index b8affa0..77a9af0 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -120,17 +120,15 @@ export default class PasteFromOffice extends Plugin { * @return {String|null} type of app which is source of a data or null */ static _getInputType( html ) { - if ( html ) { - if ( - //i.test( html ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( html ) - ) { - // Microsoft Word detection - return 'msword'; - } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { - // Google Docs detection - return 'gdocs'; - } + if ( + //i.test( html ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( html ) + ) { + // Microsoft Word detection + return 'msword'; + } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { + // Google Docs detection + return 'gdocs'; } return null; diff --git a/tests/_data/paste-from-google-docs/index.js b/tests/_data/paste-from-google-docs/index.js index 6c42bd9..91fa19a 100644 --- a/tests/_data/paste-from-google-docs/index.js +++ b/tests/_data/paste-from-google-docs/index.js @@ -4,20 +4,26 @@ */ import simpleText from './simple-text/input.html'; +import simpleTextFromFirefox from './simple-text-from-firefox/input.html'; import simpleTextNormalized from './simple-text/normalized.html'; +import simpleTextFromFirefoxNormalized from './simple-text-from-firefox/normalized.html'; import simpleTextModel from './simple-text/model.html'; +import simpleTextFromFirefoxModel from './simple-text-from-firefox/model.html'; export const fixtures = { input: { - simpleText + simpleText, + simpleTextFromFirefox }, normalized: { - simpleText: simpleTextNormalized + simpleText: simpleTextNormalized, + simpleTextFromFirefox: simpleTextFromFirefoxNormalized }, model: { - simpleText: simpleTextModel + simpleText: simpleTextModel, + simpleTextFromFirefox: simpleTextFromFirefoxModel } }; diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html new file mode 100644 index 0000000..40f58be --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html @@ -0,0 +1 @@ +

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html new file mode 100644 index 0000000..5d87fd8 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html @@ -0,0 +1 @@ +Hello world diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html new file mode 100644 index 0000000..1ef5f25 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html @@ -0,0 +1 @@ +

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html new file mode 100755 index 0000000..a1baac3 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html @@ -0,0 +1 @@ +

Hello world

\ No newline at end of file diff --git a/tests/_utils/fixtures.js b/tests/_utils/fixtures.js index d72569d..ecda8cc 100644 --- a/tests/_utils/fixtures.js +++ b/tests/_utils/fixtures.js @@ -9,7 +9,7 @@ import { fixtures as image, browserFixtures as imageBrowser } from '../_data/ima import { fixtures as link, browserFixtures as linkBrowser } from '../_data/link/index.js'; import { fixtures as list, browserFixtures as listBrowser } from '../_data/list/index.js'; import { fixtures as spacing, browserFixtures as spacingBrowser } from '../_data/spacing/index.js'; -import { fixtures as simpleText, browserFixtures as simpleTextBrowser } from '../_data/paste-from-google-docs/index'; +import { fixtures as googleDocs, browserFixtures as googleDocsBrowser } from '../_data/paste-from-google-docs/index'; // Generic fixtures. export const fixtures = { @@ -18,7 +18,7 @@ export const fixtures = { link, list, spacing, - simpleText + 'google-docs': googleDocs }; // Browser specific fixtures. @@ -28,5 +28,5 @@ export const browserFixtures = { link: linkBrowser, list: listBrowser, spacing: spacingBrowser, - simpleTextBrowser + 'google-docs': googleDocsBrowser }; diff --git a/tests/_utils/utils.js b/tests/_utils/utils.js index 2f9a331..4baf5ca 100644 --- a/tests/_utils/utils.js +++ b/tests/_utils/utils.js @@ -45,7 +45,6 @@ export function createDataTransfer( data ) { * @param {Object} config * @param {String} config.type Type of tests to generate, could be 'normalization' or 'integration'. * @param {String} config.input Name of the fixtures group. Usually stored in `/tests/_data/groupname/`. - * @param {String} config.dataSource Name of the office app, which was used to generate data. * @param {Array.} config.browsers List of all browsers for which to generate tests. * @param {Object} [config.editorConfig] Editor config which is passed to editor `create()` method. * @param {Object} [config.skip] List of fixtures for any browser to skip. The supported format is: @@ -59,10 +58,6 @@ export function generateTests( config ) { throw new Error( `Invalid tests type - \`config.type\`: '${ config.type }'.` ); } - if ( !config.dataSource ) { - throw new Error( 'No `config.dataSource` option provided.' ); - } - if ( !config.input ) { throw new Error( 'No `config.input` option provided.' ); } @@ -75,18 +70,16 @@ export function generateTests( config ) { const generateSuiteFn = config.type === 'normalization' ? generateNormalizationTests : generateIntegrationTests; describe( config.type, () => { - describe( config.dataSource, () => { - describe( config.input, () => { - const editorConfig = config.editorConfig || {}; + describe( config.input, () => { + const editorConfig = config.editorConfig || {}; - for ( const group of Object.keys( groups ) ) { - const skip = config.skip && config.skip[ group ] ? config.skip[ group ] : []; + for ( const group of Object.keys( groups ) ) { + const skip = config.skip && config.skip[ group ] ? config.skip[ group ] : []; - if ( groups[ group ] ) { - generateSuiteFn( group, groups[ group ], editorConfig, skip ); - } + if ( groups[ group ] ) { + generateSuiteFn( group, groups[ group ], editorConfig, skip ); } - } ); + } } ); } ); } diff --git a/tests/data/integration.js b/tests/data/integration.js index 25f25b6..dd1ab6f 100644 --- a/tests/data/integration.js +++ b/tests/data/integration.js @@ -26,7 +26,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'basic-styles', type: 'integration', - dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Heading, Bold, Italic, Underline, Strikethrough, PasteFromOffice ] @@ -39,7 +38,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'image', type: 'integration', - dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Image, Table, PasteFromOffice ] @@ -55,7 +53,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'link', type: 'integration', - dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Heading, Bold, Link, ShiftEnter, PasteFromOffice ] @@ -68,7 +65,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'list', type: 'integration', - dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Heading, Bold, Italic, Underline, Link, List, PasteFromOffice ] @@ -81,7 +77,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'spacing', type: 'integration', - dataSource: 'MS Word', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Bold, Italic, Underline, PasteFromOffice ] @@ -89,9 +84,8 @@ describe( 'Paste from Office - automatic', () => { } ); generateTests( { - input: 'simpleText', + input: 'google-docs', type: 'integration', - dataSource: 'Google Docs', browsers, editorConfig: { plugins: [ Clipboard, Paragraph, Bold, PasteFromOffice ] diff --git a/tests/data/normalization.js b/tests/data/normalization.js index 0840a7d..2cd8a10 100644 --- a/tests/data/normalization.js +++ b/tests/data/normalization.js @@ -18,7 +18,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'basic-styles', type: 'normalization', - dataSource: 'MS Word', browsers, editorConfig } ); @@ -26,7 +25,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'image', type: 'normalization', - dataSource: 'MS Word', browsers, editorConfig } ); @@ -34,7 +32,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'link', type: 'normalization', - dataSource: 'MS Word', browsers, editorConfig } ); @@ -42,7 +39,6 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'list', type: 'normalization', - dataSource: 'MS Word', browsers, editorConfig } ); @@ -50,15 +46,13 @@ describe( 'Paste from Office - automatic', () => { generateTests( { input: 'spacing', type: 'normalization', - dataSource: 'MS Word', browsers, editorConfig } ); generateTests( { - input: 'simpleText', + input: 'google-docs', type: 'normalization', - dataSource: 'Google Docs', browsers, editorConfig } ); diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index de5fee6..233a2f8 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -7,6 +7,7 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; import PasteFromOffice from '../src/pastefromoffice'; import { createDataTransfer } from './_utils/utils'; @@ -32,6 +33,10 @@ describe( 'Paste from Office plugin', () => { } ); } ); + afterEach( () => { + editor.destroy(); + } ); + it( 'runs normalizations if Word meta tag detected #1', () => { const dataTransfer = createDataTransfer( { 'text/html': '' @@ -88,4 +93,69 @@ describe( 'Paste from Office plugin', () => { expect( normalizeSpy.calledOnce ).to.true; } ); } ); + + describe( '_normalizeGoogleDocsInput()', () => { + before( () => { + content = new DocumentFragment(); + } ); + + beforeEach( () => { + return VirtualTestEditor + .create( { + plugins: [ Clipboard, PasteFromOffice ] + } ) + .then( newEditor => { + editor = newEditor; + normalizeSpy = testUtils.sinon.spy( PasteFromOffice, '_normalizeGoogleDocsInput' ); + } ); + } ); + + afterEach( () => { + editor.destroy(); + } ); + + it( 'runs normalizations if Google docs meta tag detected #1', () => { + const fakeClipboardData = ''; + const dataTransfer = createDataTransfer( { + 'text/html': fakeClipboardData + } ); + const htmlDataProcessor = new HtmlDataProcessor(); + const content = htmlDataProcessor.toView( fakeClipboardData ); + + editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); + + expect( normalizeSpy.calledOnce ).to.true; + } ); + } ); + + describe( '_getInputType()', () => { + [ + { + input: '', + output: 'msword' + }, + { + input: '', + output: 'msword' + }, + { + input: '', + output: null + }, + { + input: '', + output: 'gdocs' + } + ].forEach( ( value, index ) => { + it( `should return proper input type for test case: #${ index }.`, () => { + if ( value.output ) { + expect( PasteFromOffice._getInputType( value.input ) ).to.equal( value.output ); + } else { + expect( PasteFromOffice._getInputType( value.input ) ).to.be.null; + } + } ); + } ); + } ); } ); From ac4c63585dc333ff8fda2e74727c4e7cb761fb8b Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 16 Jul 2019 11:48:03 +0200 Subject: [PATCH 06/48] Unify test suits names. --- tests/filters/image.js | 152 ++++++++++++++--------------- tests/filters/list.js | 82 ++++++++-------- tests/filters/parse.js | 206 +++++++++++++++++++-------------------- tests/filters/space.js | 133 +++++++++++++------------ tests/pastefromoffice.js | 2 +- 5 files changed, 284 insertions(+), 291 deletions(-) diff --git a/tests/filters/image.js b/tests/filters/image.js index 79979de..fd0cd16 100644 --- a/tests/filters/image.js +++ b/tests/filters/image.js @@ -12,98 +12,96 @@ import { parseHtml } from '../../src/filters/parse'; import { replaceImagesSourceWithBase64, _convertHexToBase64 } from '../../src/filters/image'; import { browserFixtures } from '../_data/image/index'; -describe( 'Paste from Office', () => { - describe( 'Filters', () => { - describe( 'image', () => { - let editor; - - describe( 'replaceImagesSourceWithBase64()', () => { - describe( 'with RTF', () => { - beforeEach( () => { - return VirtualTestEditor - .create( {} ) - .then( editorInstance => { - editor = editorInstance; - } ); - } ); - - afterEach( () => { - editor.destroy(); - } ); - - it( 'should handle correctly empty RTF data', () => { - const input = '

Foo

'; - const rtfString = ''; - const { body } = parseHtml( input ); - - replaceImagesSourceWithBase64( body, rtfString, editor.editing.model ); - - expect( stringifyView( body ) ).to.equal( normalizeHtml( input ) ); - } ); - - it( 'should not change image with "http://" source', () => { - const input = '

Foo

'; - const rtfString = browserFixtures.chrome.inputRtf.onlineOffline; - const { body } = parseHtml( input ); - - replaceImagesSourceWithBase64( body, rtfString, editor.editing.model ); - - expect( stringifyView( body ) ).to.equal( normalizeHtml( input ) ); - } ); - - it( 'should not change image with "file://" source if not images in RTF data', () => { - const input = '

Foo

'; - const rtfString = '{\\rtf1\\adeflang1025\\ansi\\ansicpg1252\\uc1\\adeff31507}'; - const { body } = parseHtml( input ); - - replaceImagesSourceWithBase64( body, rtfString, editor.editing.model ); - - expect( stringifyView( body ) ).to.equal( normalizeHtml( input ) ); - } ); +describe( 'Paste from Office - filters', () => { + describe( 'image', () => { + let editor; + + describe( 'replaceImagesSourceWithBase64()', () => { + describe( 'with RTF', () => { + beforeEach( () => { + return VirtualTestEditor + .create( {} ) + .then( editorInstance => { + editor = editorInstance; + } ); } ); - } ); - - describe( '_convertHexToBase64()', () => { - it( '#1', () => { - const hex = '48656c6c6f20576f726c6421'; - const base64 = 'SGVsbG8gV29ybGQh'; - expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + afterEach( () => { + editor.destroy(); } ); - it( '#2', () => { - const hex = '466f6f204261722042617a'; - const base64 = 'Rm9vIEJhciBCYXo='; + it( 'should handle correctly empty RTF data', () => { + const input = '

Foo

'; + const rtfString = ''; + const { body } = parseHtml( input ); + + replaceImagesSourceWithBase64( body, rtfString, editor.editing.model ); - expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + expect( stringifyView( body ) ).to.equal( normalizeHtml( input ) ); } ); - it( '#3', () => { - const hex = '687474703a2f2f636b656469746f722e636f6d'; - const base64 = 'aHR0cDovL2NrZWRpdG9yLmNvbQ=='; + it( 'should not change image with "http://" source', () => { + const input = '

Foo

'; + const rtfString = browserFixtures.chrome.inputRtf.onlineOffline; + const { body } = parseHtml( input ); - expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + replaceImagesSourceWithBase64( body, rtfString, editor.editing.model ); + + expect( stringifyView( body ) ).to.equal( normalizeHtml( input ) ); } ); - it( '#4', () => { - const hex = '434B456469746F72203520697320746865206265737421'; - const base64 = 'Q0tFZGl0b3IgNSBpcyB0aGUgYmVzdCE='; + it( 'should not change image with "file://" source if not images in RTF data', () => { + const input = '

Foo

'; + const rtfString = '{\\rtf1\\adeflang1025\\ansi\\ansicpg1252\\uc1\\adeff31507}'; + const { body } = parseHtml( input ); + + replaceImagesSourceWithBase64( body, rtfString, editor.editing.model ); - expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + expect( stringifyView( body ) ).to.equal( normalizeHtml( input ) ); } ); + } ); + } ); - it( '#5', () => { - const hex = '496E74726F6475636564204D6564696120656D6265642C20626C6F636B20636F6E74656E7420696E207461626' + - 'C657320616E6420696E746567726174696F6E73207769746820416E67756C617220322B20616E642052656163742E2046' + - '696E64206F7574206D6F726520696E2074686520434B456469746F722035207631312E312E302072656C6561736564206' + - '26C6F6720706F73742E'; + describe( '_convertHexToBase64()', () => { + it( '#1', () => { + const hex = '48656c6c6f20576f726c6421'; + const base64 = 'SGVsbG8gV29ybGQh'; - const base64 = 'SW50cm9kdWNlZCBNZWRpYSBlbWJlZCwgYmxvY2sgY29udGVudCBpbiB0YWJsZXMgYW5kIGludGVncmF0aW9ucy' + - 'B3aXRoIEFuZ3VsYXIgMisgYW5kIFJlYWN0LiBGaW5kIG91dCBtb3JlIGluIHRoZSBDS0VkaXRvciA1IHYxMS4xLjAgcmVsZWF' + - 'zZWQgYmxvZyBwb3N0Lg=='; + expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + } ); - expect( _convertHexToBase64( hex ) ).to.equal( base64 ); - } ); + it( '#2', () => { + const hex = '466f6f204261722042617a'; + const base64 = 'Rm9vIEJhciBCYXo='; + + expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + } ); + + it( '#3', () => { + const hex = '687474703a2f2f636b656469746f722e636f6d'; + const base64 = 'aHR0cDovL2NrZWRpdG9yLmNvbQ=='; + + expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + } ); + + it( '#4', () => { + const hex = '434B456469746F72203520697320746865206265737421'; + const base64 = 'Q0tFZGl0b3IgNSBpcyB0aGUgYmVzdCE='; + + expect( _convertHexToBase64( hex ) ).to.equal( base64 ); + } ); + + it( '#5', () => { + const hex = '496E74726F6475636564204D6564696120656D6265642C20626C6F636B20636F6E74656E7420696E207461626' + + 'C657320616E6420696E746567726174696F6E73207769746820416E67756C617220322B20616E642052656163742E2046' + + '696E64206F7574206D6F726520696E2074686520434B456469746F722035207631312E312E302072656C6561736564206' + + '26C6F6720706F73742E'; + + const base64 = 'SW50cm9kdWNlZCBNZWRpYSBlbWJlZCwgYmxvY2sgY29udGVudCBpbiB0YWJsZXMgYW5kIGludGVncmF0aW9ucy' + + 'B3aXRoIEFuZ3VsYXIgMisgYW5kIFJlYWN0LiBGaW5kIG91dCBtb3JlIGluIHRoZSBDS0VkaXRvciA1IHYxMS4xLjAgcmVsZWF' + + 'zZWQgYmxvZyBwb3N0Lg=='; + + expect( _convertHexToBase64( hex ) ).to.equal( base64 ); } ); } ); } ); diff --git a/tests/filters/list.js b/tests/filters/list.js index a8d7617..09fdbc2 100644 --- a/tests/filters/list.js +++ b/tests/filters/list.js @@ -9,63 +9,61 @@ import View from '@ckeditor/ckeditor5-engine/src/view/view'; import { transformListItemLikeElementsIntoLists } from '../../src/filters/list'; -describe( 'Paste from Office', () => { - describe( 'Filters', () => { - describe( 'list', () => { - const htmlDataProcessor = new HtmlDataProcessor(); +describe( 'Paste from Office - filters', () => { + describe( 'list', () => { + const htmlDataProcessor = new HtmlDataProcessor(); - describe( 'transformListItemLikeElementsIntoLists()', () => { - it( 'replaces list-like elements with semantic lists', () => { - const html = '

1.Item 1

'; - const view = htmlDataProcessor.toView( html ); + describe( 'transformListItemLikeElementsIntoLists()', () => { + it( 'replaces list-like elements with semantic lists', () => { + const html = '

1.Item 1

'; + const view = htmlDataProcessor.toView( html ); - transformListItemLikeElementsIntoLists( view, '', new View() ); + transformListItemLikeElementsIntoLists( view, '', new View() ); - expect( view.childCount ).to.equal( 1 ); - expect( view.getChild( 0 ).name ).to.equal( 'ol' ); - expect( stringify( view ) ).to.equal( '
  1. Item 1
' ); - } ); + expect( view.childCount ).to.equal( 1 ); + expect( view.getChild( 0 ).name ).to.equal( 'ol' ); + expect( stringify( view ) ).to.equal( '
  1. Item 1
' ); + } ); - it( 'replaces list-like elements with semantic lists with proper bullet type based on styles', () => { - const html = '

1.Item 1

'; - const view = htmlDataProcessor.toView( html ); + it( 'replaces list-like elements with semantic lists with proper bullet type based on styles', () => { + const html = '

1.Item 1

'; + const view = htmlDataProcessor.toView( html ); - transformListItemLikeElementsIntoLists( view, '@list l0:level1 { mso-level-number-format: bullet; }', new View() ); + transformListItemLikeElementsIntoLists( view, '@list l0:level1 { mso-level-number-format: bullet; }', new View() ); - expect( view.childCount ).to.equal( 1 ); - expect( view.getChild( 0 ).name ).to.equal( 'ul' ); - expect( stringify( view ) ).to.equal( '
  • Item 1
' ); - } ); + expect( view.childCount ).to.equal( 1 ); + expect( view.getChild( 0 ).name ).to.equal( 'ul' ); + expect( stringify( view ) ).to.equal( '
  • Item 1
' ); + } ); - it( 'does not modify the view if there are no list-like elements', () => { - const html = '

H1

Foo Bar

'; - const view = htmlDataProcessor.toView( html ); + it( 'does not modify the view if there are no list-like elements', () => { + const html = '

H1

Foo Bar

'; + const view = htmlDataProcessor.toView( html ); - transformListItemLikeElementsIntoLists( view, '', new View() ); + transformListItemLikeElementsIntoLists( view, '', new View() ); - expect( view.childCount ).to.equal( 2 ); - expect( stringify( view ) ).to.equal( html ); - } ); + expect( view.childCount ).to.equal( 2 ); + expect( stringify( view ) ).to.equal( html ); + } ); - it( 'handles empty `mso-list` style correctly', () => { - const html = '

1.Item 1

'; - const view = htmlDataProcessor.toView( html ); + it( 'handles empty `mso-list` style correctly', () => { + const html = '

1.Item 1

'; + const view = htmlDataProcessor.toView( html ); - transformListItemLikeElementsIntoLists( view, '', new View() ); + transformListItemLikeElementsIntoLists( view, '', new View() ); - expect( view.childCount ).to.equal( 1 ); - expect( view.getChild( 0 ).name ).to.equal( 'ol' ); - expect( stringify( view ) ).to.equal( '
  1. Item 1
' ); - } ); + expect( view.childCount ).to.equal( 1 ); + expect( view.getChild( 0 ).name ).to.equal( 'ol' ); + expect( stringify( view ) ).to.equal( '
  1. Item 1
' ); + } ); - it( 'handles empty body correctly', () => { - const view = htmlDataProcessor.toView( '' ); + it( 'handles empty body correctly', () => { + const view = htmlDataProcessor.toView( '' ); - transformListItemLikeElementsIntoLists( view, '', new View() ); + transformListItemLikeElementsIntoLists( view, '', new View() ); - expect( view.childCount ).to.equal( 0 ); - expect( stringify( view ) ).to.equal( '' ); - } ); + expect( view.childCount ).to.equal( 0 ); + expect( stringify( view ) ).to.equal( '' ); } ); } ); } ); diff --git a/tests/filters/parse.js b/tests/filters/parse.js index b7e61f9..8a5e9a8 100644 --- a/tests/filters/parse.js +++ b/tests/filters/parse.js @@ -9,159 +9,157 @@ import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragme import { parseHtml } from '../../src/filters/parse'; -describe( 'Paste from Office', () => { - describe( 'Filters', () => { - describe( 'parse', () => { - describe( 'parseHtml()', () => { - it( 'correctly parses HTML with body and one style tag', () => { - const html = '

Foo Bar

'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); +describe( 'Paste from Office - filters', () => { + describe( 'parse', () => { + describe( 'parseHtml()', () => { + it( 'correctly parses HTML with body and one style tag', () => { + const html = '

Foo Bar

'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1, 'body.childCount' ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1, 'body.childCount' ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 1, 'styles.length' ); - expect( styles[ 0 ] ).to.instanceof( CSSStyleSheet ); - expect( styles[ 0 ].cssRules.length ).to.equal( 2 ); - expect( styles[ 0 ].cssRules[ 0 ].style.color ).to.equal( 'red' ); - expect( styles[ 0 ].cssRules[ 1 ].style[ 'font-size' ] ).to.equal( '12px' ); + expect( styles.length ).to.equal( 1, 'styles.length' ); + expect( styles[ 0 ] ).to.instanceof( CSSStyleSheet ); + expect( styles[ 0 ].cssRules.length ).to.equal( 2 ); + expect( styles[ 0 ].cssRules[ 0 ].style.color ).to.equal( 'red' ); + expect( styles[ 0 ].cssRules[ 1 ].style[ 'font-size' ] ).to.equal( '12px' ); - expect( stylesString ).to.equal( 'p { color: red; } a { font-size: 12px; }' ); - } ); + expect( stylesString ).to.equal( 'p { color: red; } a { font-size: 12px; }' ); + } ); - it( 'correctly parses HTML with body contents only', () => { - const html = '

Foo Bar

'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'correctly parses HTML with body contents only', () => { + const html = '

Foo Bar

'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1 ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1 ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'correctly parses HTML with no body and multiple style tags', () => { - const html = ''; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'correctly parses HTML with no body and multiple style tags', () => { + const html = ''; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 0 ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 0 ); - expect( bodyString ).to.equal( '' ); + expect( bodyString ).to.equal( '' ); - expect( styles.length ).to.equal( 2 ); - expect( styles[ 0 ] ).to.instanceof( CSSStyleSheet ); - expect( styles[ 1 ] ).to.instanceof( CSSStyleSheet ); - expect( styles[ 0 ].cssRules.length ).to.equal( 1 ); - expect( styles[ 1 ].cssRules.length ).to.equal( 1 ); - expect( styles[ 0 ].cssRules[ 0 ].style.color ).to.equal( 'blue' ); - expect( styles[ 1 ].cssRules[ 0 ].style.color ).to.equal( 'green' ); + expect( styles.length ).to.equal( 2 ); + expect( styles[ 0 ] ).to.instanceof( CSSStyleSheet ); + expect( styles[ 1 ] ).to.instanceof( CSSStyleSheet ); + expect( styles[ 0 ].cssRules.length ).to.equal( 1 ); + expect( styles[ 1 ].cssRules.length ).to.equal( 1 ); + expect( styles[ 0 ].cssRules[ 0 ].style.color ).to.equal( 'blue' ); + expect( styles[ 1 ].cssRules[ 0 ].style.color ).to.equal( 'green' ); - expect( stylesString ).to.equal( 'p { color: blue; } a { color: green; }' ); - } ); + expect( stylesString ).to.equal( 'p { color: blue; } a { color: green; }' ); + } ); - it( 'correctly parses HTML with no body and no style tags', () => { - const html = ''; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'correctly parses HTML with no body and no style tags', () => { + const html = ''; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 0 ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 0 ); - expect( bodyString ).to.equal( '' ); + expect( bodyString ).to.equal( '' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'correctly parses HTML with body contents and empty style tag', () => { - const html = '

Foo Bar

'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'correctly parses HTML with body contents and empty style tag', () => { + const html = '

Foo Bar

'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1 ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1 ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'should remove any content after body closing tag - plain', () => { - const html = '

Foo Bar

Ba'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'should remove any content after body closing tag - plain', () => { + const html = '

Foo Bar

Ba'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1, 'body.childCount' ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1, 'body.childCount' ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'should remove any content after body closing tag - inline', () => { - const html = '

Foo Bar

Fo'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'should remove any content after body closing tag - inline', () => { + const html = '

Foo Bar

Fo'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1, 'body.childCount' ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1, 'body.childCount' ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'should remove any content after body closing tag - block', () => { - const html = '

Foo Bar

ar

'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'should remove any content after body closing tag - block', () => { + const html = '

Foo Bar

ar

'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1, 'body.childCount' ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1, 'body.childCount' ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'should remove any content after body closing tag - no html tag', () => { - const html = '

Foo Bar

oo'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'should remove any content after body closing tag - no html tag', () => { + const html = '

Foo Bar

oo'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 1, 'body.childCount' ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 1, 'body.childCount' ); - expect( bodyString ).to.equal( '

Foo Bar

' ); + expect( bodyString ).to.equal( '

Foo Bar

' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); + } ); - it( 'should not remove any content if no body tag', () => { - const html = '

Foo Bar

Baz'; - const { body, bodyString, styles, stylesString } = parseHtml( html ); + it( 'should not remove any content if no body tag', () => { + const html = '

Foo Bar

Baz'; + const { body, bodyString, styles, stylesString } = parseHtml( html ); - expect( body ).to.instanceof( DocumentFragment ); - expect( body.childCount ).to.equal( 2, 'body.childCount' ); + expect( body ).to.instanceof( DocumentFragment ); + expect( body.childCount ).to.equal( 2, 'body.childCount' ); - expect( bodyString ).to.equal( '

Foo Bar

Baz' ); + expect( bodyString ).to.equal( '

Foo Bar

Baz' ); - expect( styles.length ).to.equal( 0 ); + expect( styles.length ).to.equal( 0 ); - expect( stylesString ).to.equal( '' ); - } ); + expect( stylesString ).to.equal( '' ); } ); } ); } ); diff --git a/tests/filters/space.js b/tests/filters/space.js index 93a8607..4ef2550 100644 --- a/tests/filters/space.js +++ b/tests/filters/space.js @@ -6,100 +6,99 @@ /* globals DOMParser */ import { normalizeSpacing, normalizeSpacerunSpans } from '../../src/filters/space'; -describe( 'Paste from Office', () => { - describe( 'Filters', () => { - describe( 'space', () => { - describe( 'normalizeSpacing()', () => { - it( 'should replace last space before closing tag with NBSP', () => { - const input = '

Foo

Bar Baz

'; - const expected = '

Foo\u00A0

Bar \u00A0 Baz\u00A0

'; - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); +describe( 'Paste from Office - filters', () => { + describe( 'space', () => { + describe( 'normalizeSpacing()', () => { + it( 'should replace last space before closing tag with NBSP', () => { + const input = '

Foo

Bar Baz

'; + const expected = '

Foo\u00A0

Bar \u00A0 Baz\u00A0

'; - it( 'should replace last space before special "o:p" tag with NBSP', () => { - const input = '

Foo Bar

'; - const expected = '

Foo \u00A0\u00A0 Bar

'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should replace last space before special "o:p" tag with NBSP', () => { + const input = '

Foo Bar

'; + const expected = '

Foo \u00A0\u00A0 Bar

'; - it( 'should remove newlines from spacerun spans #1', () => { - const input = ' \n'; - const expected = ' \u00A0'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should remove newlines from spacerun spans #1', () => { + const input = ' \n'; + const expected = ' \u00A0'; - it( 'should remove newlines from spacerun spans #2', () => { - const input = ' \r\n'; - const expected = '\u00A0'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should remove newlines from spacerun spans #2', () => { + const input = ' \r\n'; + const expected = '\u00A0'; - it( 'should remove newlines from spacerun spans #3', () => { - const input = ' \r\n\n '; - const expected = ' \u00A0'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should remove newlines from spacerun spans #3', () => { + const input = ' \r\n\n '; + const expected = ' \u00A0'; - it( 'should remove newlines from spacerun spans #4', () => { - const input = '\n\n\n '; - const expected = ' \u00A0'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should remove newlines from spacerun spans #4', () => { + const input = '\n\n\n '; + const expected = ' \u00A0'; - it( 'should remove newlines from spacerun spans #5', () => { - const input = '\n\n'; - const expected = ''; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should remove newlines from spacerun spans #5', () => { + const input = '\n\n'; + const expected = ''; - it( 'should remove multiline sequences of whitespaces', () => { - const input = '

Foo

\n\n \n

Bar

\r\n\r\n

Baz

'; - const expected = '

Foo

Bar

Baz

'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should remove multiline sequences of whitespaces', () => { + const input = '

Foo

\n\n \n

Bar

\r\n\r\n

Baz

'; + const expected = '

Foo

Bar

Baz

'; - it( 'should normalize Safari "space spans"', () => { - const input = '

Foo Baz

'; - const expected = '

Foo \u00A0 \u00A0 Baz \u00A0\u00A0

'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + it( 'should normalize Safari "space spans"', () => { + const input = '

Foo Baz

'; + const expected = '

Foo \u00A0 \u00A0 Baz \u00A0\u00A0

'; - it( 'should normalize nested Safari "space spans"', () => { - const input = - '

Foo Baz

'; + expect( normalizeSpacing( input ) ).to.equal( expected ); + } ); - const expected = '

Foo \u00A0 \u00A0 \u00A0 Baz

'; + it( 'should normalize nested Safari "space spans"', () => { + const input = + '

Foo Baz

'; - expect( normalizeSpacing( input ) ).to.equal( expected ); - } ); + const expected = '

Foo \u00A0 \u00A0 \u00A0 Baz

'; + + expect( normalizeSpacing( input ) ).to.equal( expected ); } ); + } ); - describe( 'normalizeSpacerunSpans()', () => { - it( 'should normalize spaces inside special "span.spacerun" elements', () => { - const input = '

Foo

' + - '

Baz

'; + describe( 'normalizeSpacerunSpans()', () => { + it( 'should normalize spaces inside special "span.spacerun" elements', () => { + const input = '

Foo

' + + '

Baz

'; - const expected = '

   Foo

' + - '

Baz      

'; + const expected = '

   Foo

' + + '

Baz      

'; - const domParser = new DOMParser(); - const htmlDocument = domParser.parseFromString( input, 'text/html' ); + const domParser = new DOMParser(); + const htmlDocument = domParser.parseFromString( input, 'text/html' ); - expect( htmlDocument.body.innerHTML.replace( /'/g, '"' ).replace( /: /g, ':' ) ).to.not.equal( expected ); + expect( htmlDocument.body.innerHTML.replace( /'/g, '"' ).replace( /: /g, ':' ) ).to.not.equal( expected ); - normalizeSpacerunSpans( htmlDocument ); + normalizeSpacerunSpans( htmlDocument ); - expect( htmlDocument.body.innerHTML.replace( /'/g, '"' ).replace( /: /g, ':' ) ).to.equal( expected ); - } ); + expect( htmlDocument.body.innerHTML.replace( /'/g, '"' ).replace( /: /g, ':' ) ).to.equal( expected ); } ); } ); } ); diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 233a2f8..ea29d0e 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -12,7 +12,7 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/html import PasteFromOffice from '../src/pastefromoffice'; import { createDataTransfer } from './_utils/utils'; -describe( 'Paste from Office plugin', () => { +describe( 'Paste from Office - plugin', () => { let editor, content, normalizeSpy; testUtils.createSinonSandbox(); From f3dbf34ca046c352b1d32b2c31b4555975ed4758 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 16 Jul 2019 12:41:13 +0200 Subject: [PATCH 07/48] Remove unnecessary autoamtic test which is cover now by filter test. --- tests/_data/paste-from-google-docs/index.js | 12 ++--- .../simple-text-from-firefox/input.html | 1 - .../simple-text-from-firefox/model.html | 1 - .../simple-text-from-firefox/normalized.html | 1 - .../simple-text-from-firefox.html | 1 - .../simple-text/input.html | 2 +- tests/filters/common.js | 45 +++++++++++++++++++ 7 files changed, 49 insertions(+), 14 deletions(-) delete mode 100644 tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html delete mode 100644 tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html delete mode 100644 tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html delete mode 100755 tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html create mode 100644 tests/filters/common.js diff --git a/tests/_data/paste-from-google-docs/index.js b/tests/_data/paste-from-google-docs/index.js index 91fa19a..6c42bd9 100644 --- a/tests/_data/paste-from-google-docs/index.js +++ b/tests/_data/paste-from-google-docs/index.js @@ -4,26 +4,20 @@ */ import simpleText from './simple-text/input.html'; -import simpleTextFromFirefox from './simple-text-from-firefox/input.html'; import simpleTextNormalized from './simple-text/normalized.html'; -import simpleTextFromFirefoxNormalized from './simple-text-from-firefox/normalized.html'; import simpleTextModel from './simple-text/model.html'; -import simpleTextFromFirefoxModel from './simple-text-from-firefox/model.html'; export const fixtures = { input: { - simpleText, - simpleTextFromFirefox + simpleText }, normalized: { - simpleText: simpleTextNormalized, - simpleTextFromFirefox: simpleTextFromFirefoxNormalized + simpleText: simpleTextNormalized }, model: { - simpleText: simpleTextModel, - simpleTextFromFirefox: simpleTextFromFirefoxModel + simpleText: simpleTextModel } }; diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html deleted file mode 100644 index 40f58be..0000000 --- a/tests/_data/paste-from-google-docs/simple-text-from-firefox/input.html +++ /dev/null @@ -1 +0,0 @@ -

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html deleted file mode 100644 index 5d87fd8..0000000 --- a/tests/_data/paste-from-google-docs/simple-text-from-firefox/model.html +++ /dev/null @@ -1 +0,0 @@ -Hello world diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html deleted file mode 100644 index 1ef5f25..0000000 --- a/tests/_data/paste-from-google-docs/simple-text-from-firefox/normalized.html +++ /dev/null @@ -1 +0,0 @@ -

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html b/tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html deleted file mode 100755 index a1baac3..0000000 --- a/tests/_data/paste-from-google-docs/simple-text-from-firefox/simple-text-from-firefox.html +++ /dev/null @@ -1 +0,0 @@ -

Hello world

\ No newline at end of file diff --git a/tests/_data/paste-from-google-docs/simple-text/input.html b/tests/_data/paste-from-google-docs/simple-text/input.html index 7027a14..0037153 100644 --- a/tests/_data/paste-from-google-docs/simple-text/input.html +++ b/tests/_data/paste-from-google-docs/simple-text/input.html @@ -1 +1 @@ -

Hello world


+

Hello world


diff --git a/tests/filters/common.js b/tests/filters/common.js new file mode 100644 index 0000000..5a1cdac --- /dev/null +++ b/tests/filters/common.js @@ -0,0 +1,45 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import { removeBoldTagWrapper } from '../../src/filters/common'; + +describe( 'Paste from Office - filters', () => { + const htmlDataProcessor = new HtmlDataProcessor(); + describe( 'common', () => { + describe( 'removeBoldTagWrapper', () => { + it( 'should remove bold wrapper added by google docs', () => { + const inputData = '' + + '

Hello world

' + + '
'; + const documentFragment = htmlDataProcessor.toView( inputData ); + + removeBoldTagWrapper( documentFragment ); + + expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); + } ); + + it( 'should not remove non-bold tag with google id', () => { + const inputData = '

Hello world

'; + const documentFragment = htmlDataProcessor.toView( inputData ); + + removeBoldTagWrapper( documentFragment ); + + expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( + '

Hello world

' ); + } ); + + it( 'should not remove bold tag without google id', () => { + const inputData = 'Hello world'; + const documentFragment = htmlDataProcessor.toView( inputData ); + + removeBoldTagWrapper( documentFragment ); + + expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( + 'Hello world' ); + } ); + } ); + } ); +} ); From 1900a9fe4a83ac23f460da115bcae528fa47d4fd Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 16 Jul 2019 14:16:57 +0200 Subject: [PATCH 08/48] Add clipboard input from Firefox which is different from Chrome. Small typo fixes. --- src/filters/common.js | 4 ++-- src/pastefromoffice.js | 20 ++++++++++--------- tests/_data/paste-from-google-docs/index.js | 18 ++++++++++++++++- .../simple-text/input.firefox.html | 1 + .../simple-text/normalized.firefox.html | 1 + 5 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 tests/_data/paste-from-google-docs/simple-text/input.firefox.html create mode 100644 tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html diff --git a/src/filters/common.js b/src/filters/common.js index 3d13fdd..5976272 100644 --- a/src/filters/common.js +++ b/src/filters/common.js @@ -8,7 +8,7 @@ */ /** - * Removes tag wrapper added by Google Docs for copied content. + * The filter removes `` tag wrapper added by Google Docs for copied content. * * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment * @returns {module:engine/view/documentfragment~DocumentFragment} @@ -16,7 +16,7 @@ export function removeBoldTagWrapper( documentFragment ) { const firstChild = documentFragment.getChild( 0 ); - if ( firstChild.name === 'b' && firstChild.getStyle( 'font-weight' ) === 'normal' ) { + if ( firstChild && firstChild.is( 'b' ) && firstChild.getStyle( 'font-weight' ) === 'normal' ) { const children = firstChild.getChildren(); documentFragment._removeChildren( 0 ); diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 77a9af0..c2139f0 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -47,14 +47,14 @@ export default class PasteFromOffice extends Plugin { } /** - * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} event. Detects if content comes - * from recognized source and normalize it. + * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation `inputTransformation` event}. + * Detects if content comes from a recognized source and normalize it. * * **Note**: this function was exposed mainly for testing purposes and should not be called directly. * * @private * @param {module:utils/eventinfo~EventInfo} evt - * @param {Object} data passed with {@link module:clipboard/clipboard~Clipboard#event:inputTransformation} + * @param {Object} data same structure like {@link module:clipboard/clipboard~Clipboard#event:inputTransformation input transformation} */ static _inputTransformationListener( evt, data ) { const html = data.dataTransfer.getData( 'text/html' ); @@ -110,14 +110,16 @@ export default class PasteFromOffice extends Plugin { } /** - * Determines if given paste data came from specific office-like application - * Recognized are: - * * 'msword' for Microsoft Words desktop app - * * 'gdocs' for Google Docs online app + * Determines if given paste data came from the specific office-like application. + * Currently recognized are: + * * `'msword'` for Microsoft Words desktop app + * * `'gdocs'` for Google Docs online app + * + * **Note**: this function is exposed mainly for testing purposes and should not be called directly. * * @private - * @param {String} html `text/html` string from data transfer - * @return {String|null} type of app which is source of a data or null + * @param {String} html the `text/html` string from data transfer + * @return {String|null} name of source app of an html data or null */ static _getInputType( html ) { if ( diff --git a/tests/_data/paste-from-google-docs/index.js b/tests/_data/paste-from-google-docs/index.js index 6c42bd9..0d834c5 100644 --- a/tests/_data/paste-from-google-docs/index.js +++ b/tests/_data/paste-from-google-docs/index.js @@ -21,4 +21,20 @@ export const fixtures = { } }; -export const browserFixtures = {}; +import simpleTextFirefox from './simple-text/input.firefox.html'; + +import simpleTextNormalizedFirefox from './simple-text/normalized.firefox.html'; + +export const browserFixtures = { + firefox: { + input: { + simpleText: simpleTextFirefox + }, + normalized: { + simpleText: simpleTextNormalizedFirefox + }, + model: { + simpleText: simpleTextModel + } + } +}; diff --git a/tests/_data/paste-from-google-docs/simple-text/input.firefox.html b/tests/_data/paste-from-google-docs/simple-text/input.firefox.html new file mode 100644 index 0000000..4efc799 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text/input.firefox.html @@ -0,0 +1 @@ +

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html b/tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html new file mode 100644 index 0000000..98b7826 --- /dev/null +++ b/tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html @@ -0,0 +1 @@ +

Hello world

From 529b6e3bbc6bca71395d107f757c5f5fd9958dd3 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 16 Jul 2019 14:47:31 +0200 Subject: [PATCH 09/48] Simplify test names. --- tests/data/integration.js | 2 +- tests/data/normalization.js | 2 +- tests/filters/common.js | 2 +- tests/filters/image.js | 2 +- tests/filters/list.js | 2 +- tests/filters/parse.js | 2 +- tests/filters/space.js | 2 +- tests/pastefromoffice.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/data/integration.js b/tests/data/integration.js index dd1ab6f..23d4b3a 100644 --- a/tests/data/integration.js +++ b/tests/data/integration.js @@ -22,7 +22,7 @@ import { generateTests } from '../_utils/utils'; const browsers = [ 'chrome', 'firefox', 'safari', 'edge' ]; -describe( 'Paste from Office - automatic', () => { +describe( 'PasteFromOffice/data - automatic', () => { generateTests( { input: 'basic-styles', type: 'integration', diff --git a/tests/data/normalization.js b/tests/data/normalization.js index 2cd8a10..b76bca9 100644 --- a/tests/data/normalization.js +++ b/tests/data/normalization.js @@ -14,7 +14,7 @@ const editorConfig = { plugins: [ Clipboard, PasteFromOffice ] }; -describe( 'Paste from Office - automatic', () => { +describe( 'PasteFromOffice/data - automatic', () => { generateTests( { input: 'basic-styles', type: 'normalization', diff --git a/tests/filters/common.js b/tests/filters/common.js index 5a1cdac..e9abb59 100644 --- a/tests/filters/common.js +++ b/tests/filters/common.js @@ -6,7 +6,7 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; import { removeBoldTagWrapper } from '../../src/filters/common'; -describe( 'Paste from Office - filters', () => { +describe( 'PasteFromOffice/filters', () => { const htmlDataProcessor = new HtmlDataProcessor(); describe( 'common', () => { describe( 'removeBoldTagWrapper', () => { diff --git a/tests/filters/image.js b/tests/filters/image.js index fd0cd16..542114f 100644 --- a/tests/filters/image.js +++ b/tests/filters/image.js @@ -12,7 +12,7 @@ import { parseHtml } from '../../src/filters/parse'; import { replaceImagesSourceWithBase64, _convertHexToBase64 } from '../../src/filters/image'; import { browserFixtures } from '../_data/image/index'; -describe( 'Paste from Office - filters', () => { +describe( 'PasteFromOffice/filters', () => { describe( 'image', () => { let editor; diff --git a/tests/filters/list.js b/tests/filters/list.js index 09fdbc2..1a1c95a 100644 --- a/tests/filters/list.js +++ b/tests/filters/list.js @@ -9,7 +9,7 @@ import View from '@ckeditor/ckeditor5-engine/src/view/view'; import { transformListItemLikeElementsIntoLists } from '../../src/filters/list'; -describe( 'Paste from Office - filters', () => { +describe( 'PasteFromOffice/filters', () => { describe( 'list', () => { const htmlDataProcessor = new HtmlDataProcessor(); diff --git a/tests/filters/parse.js b/tests/filters/parse.js index 8a5e9a8..13dadf7 100644 --- a/tests/filters/parse.js +++ b/tests/filters/parse.js @@ -9,7 +9,7 @@ import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragme import { parseHtml } from '../../src/filters/parse'; -describe( 'Paste from Office - filters', () => { +describe( 'PasteFromOffice/filters', () => { describe( 'parse', () => { describe( 'parseHtml()', () => { it( 'correctly parses HTML with body and one style tag', () => { diff --git a/tests/filters/space.js b/tests/filters/space.js index 4ef2550..102f1db 100644 --- a/tests/filters/space.js +++ b/tests/filters/space.js @@ -7,7 +7,7 @@ import { normalizeSpacing, normalizeSpacerunSpans } from '../../src/filters/space'; -describe( 'Paste from Office - filters', () => { +describe( 'PasteFromOffice/filters', () => { describe( 'space', () => { describe( 'normalizeSpacing()', () => { it( 'should replace last space before closing tag with NBSP', () => { diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index ea29d0e..290d34a 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -12,7 +12,7 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/html import PasteFromOffice from '../src/pastefromoffice'; import { createDataTransfer } from './_utils/utils'; -describe( 'Paste from Office - plugin', () => { +describe( 'PasteFromOffice', () => { let editor, content, normalizeSpy; testUtils.createSinonSandbox(); From f25f38ccf760f101e6ed6993200af2de76696d97 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Thu, 18 Jul 2019 15:52:42 +0200 Subject: [PATCH 10/48] Fix bold removal on windows. Add new test cover it. Change tests location. --- src/filters/common.js | 12 ++--- .../bold-wrapper/index.js | 51 +++++++++++++++++++ .../simple-text-windows/input.firefox.html | 4 ++ .../simple-text-windows/input.html | 5 ++ .../simple-text-windows}/model.html | 0 .../normalized.firefox.html | 2 +- .../simple-text-windows}/normalized.html | 0 .../simple-text-windows.html | 1 + .../simple-text/input.firefox.html | 2 +- .../{ => bold-wrapper}/simple-text/input.html | 2 +- .../bold-wrapper/simple-text/model.html | 1 + .../simple-text/normalized.firefox.html | 1 + .../bold-wrapper/simple-text/normalized.html | 1 + .../simple-text/simple-text.html | 0 tests/_data/paste-from-google-docs/index.js | 40 --------------- tests/_utils/fixtures.js | 7 +-- tests/_utils/utils.js | 4 +- tests/data/integration.js | 2 +- tests/data/normalization.js | 2 +- 19 files changed, 81 insertions(+), 56 deletions(-) create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/index.js create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.firefox.html create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.html rename tests/_data/paste-from-google-docs/{simple-text => bold-wrapper/simple-text-windows}/model.html (100%) rename tests/_data/paste-from-google-docs/{simple-text => bold-wrapper/simple-text-windows}/normalized.firefox.html (82%) rename tests/_data/paste-from-google-docs/{simple-text => bold-wrapper/simple-text-windows}/normalized.html (100%) create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/simple-text-windows.html rename tests/_data/paste-from-google-docs/{ => bold-wrapper}/simple-text/input.firefox.html (68%) rename tests/_data/paste-from-google-docs/{ => bold-wrapper}/simple-text/input.html (87%) create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/simple-text/model.html create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.firefox.html create mode 100644 tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.html rename tests/_data/paste-from-google-docs/{ => bold-wrapper}/simple-text/simple-text.html (100%) delete mode 100644 tests/_data/paste-from-google-docs/index.js diff --git a/src/filters/common.js b/src/filters/common.js index 5976272..9728842 100644 --- a/src/filters/common.js +++ b/src/filters/common.js @@ -14,13 +14,13 @@ * @returns {module:engine/view/documentfragment~DocumentFragment} */ export function removeBoldTagWrapper( documentFragment ) { - const firstChild = documentFragment.getChild( 0 ); + for ( const childWithWrapper of documentFragment.getChildren() ) { + if ( childWithWrapper.is( 'b' ) && childWithWrapper.getStyle( 'font-weight' ) === 'normal' ) { + const childIndex = documentFragment.getChildIndex( childWithWrapper ); - if ( firstChild && firstChild.is( 'b' ) && firstChild.getStyle( 'font-weight' ) === 'normal' ) { - const children = firstChild.getChildren(); - - documentFragment._removeChildren( 0 ); - documentFragment._insertChild( 0, children ); + documentFragment._removeChildren( childIndex ); + documentFragment._insertChild( childIndex, childWithWrapper.getChildren() ); + } } return documentFragment; diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/index.js b/tests/_data/paste-from-google-docs/bold-wrapper/index.js new file mode 100644 index 0000000..61a3818 --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/index.js @@ -0,0 +1,51 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import simpleText from './simple-text/input.html'; +import simpleTextWindows from './simple-text-windows/input.html'; + +import simpleTextNormalized from './simple-text/normalized.html'; +import simpleTextWindowsNormalized from './simple-text-windows/normalized.html'; + +import simpleTextModel from './simple-text/model.html'; +import simpleTextWindowsModel from './simple-text-windows/model.html'; + +export const fixtures = { + input: { + simpleText, + simpleTextWindows + }, + normalized: { + simpleText: simpleTextNormalized, + simpleTextWindows: simpleTextWindowsNormalized + }, + model: { + simpleText: simpleTextModel, + simpleTextWindows: simpleTextWindowsModel + } +}; + +import simpleTextFirefox from './simple-text/input.firefox.html'; +import simpleTextWindowsFirefox from './simple-text-windows/input.firefox.html'; + +import simpleTextNormalizedFirefox from './simple-text/normalized.firefox.html'; +import simpleTextWindowsNormalizedFirefox from './simple-text-windows/normalized.firefox.html'; + +export const browserFixtures = { + firefox: { + input: { + simpleText: simpleTextFirefox, + simpleTextWindows: simpleTextWindowsFirefox + }, + normalized: { + simpleText: simpleTextNormalizedFirefox, + simpleTextWindows: simpleTextWindowsNormalizedFirefox + }, + model: { + simpleText: simpleTextModel, + simpleTextWindows: simpleTextWindowsModel + } + } +}; diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.firefox.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.firefox.html new file mode 100644 index 0000000..5187a76 --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.firefox.html @@ -0,0 +1,4 @@ + +

Hello world

+ + diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.html new file mode 100644 index 0000000..50e8d90 --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/input.html @@ -0,0 +1,5 @@ + + +

Hello world


+ + diff --git a/tests/_data/paste-from-google-docs/simple-text/model.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/model.html similarity index 100% rename from tests/_data/paste-from-google-docs/simple-text/model.html rename to tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/model.html diff --git a/tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/normalized.firefox.html similarity index 82% rename from tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html rename to tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/normalized.firefox.html index 98b7826..c207b2c 100644 --- a/tests/_data/paste-from-google-docs/simple-text/normalized.firefox.html +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/normalized.firefox.html @@ -1 +1 @@ -

Hello world

+

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text/normalized.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/normalized.html similarity index 100% rename from tests/_data/paste-from-google-docs/simple-text/normalized.html rename to tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/normalized.html diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/simple-text-windows.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/simple-text-windows.html new file mode 100644 index 0000000..8f73e66 --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text-windows/simple-text-windows.html @@ -0,0 +1 @@ +

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text/input.firefox.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/input.firefox.html similarity index 68% rename from tests/_data/paste-from-google-docs/simple-text/input.firefox.html rename to tests/_data/paste-from-google-docs/bold-wrapper/simple-text/input.firefox.html index 4efc799..e698d5e 100644 --- a/tests/_data/paste-from-google-docs/simple-text/input.firefox.html +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/input.firefox.html @@ -1 +1 @@ -

Hello world

+

Hello world

diff --git a/tests/_data/paste-from-google-docs/simple-text/input.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/input.html similarity index 87% rename from tests/_data/paste-from-google-docs/simple-text/input.html rename to tests/_data/paste-from-google-docs/bold-wrapper/simple-text/input.html index 0037153..297defb 100644 --- a/tests/_data/paste-from-google-docs/simple-text/input.html +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/input.html @@ -1 +1 @@ -

Hello world


+

Hello world


diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/model.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/model.html new file mode 100644 index 0000000..5d87fd8 --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/model.html @@ -0,0 +1 @@ +Hello world diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.firefox.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.firefox.html new file mode 100644 index 0000000..491774a --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.firefox.html @@ -0,0 +1 @@ +

Hello world

diff --git a/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.html new file mode 100644 index 0000000..7ed6144 --- /dev/null +++ b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/normalized.html @@ -0,0 +1 @@ +

Hello world


diff --git a/tests/_data/paste-from-google-docs/simple-text/simple-text.html b/tests/_data/paste-from-google-docs/bold-wrapper/simple-text/simple-text.html similarity index 100% rename from tests/_data/paste-from-google-docs/simple-text/simple-text.html rename to tests/_data/paste-from-google-docs/bold-wrapper/simple-text/simple-text.html diff --git a/tests/_data/paste-from-google-docs/index.js b/tests/_data/paste-from-google-docs/index.js deleted file mode 100644 index 0d834c5..0000000 --- a/tests/_data/paste-from-google-docs/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -import simpleText from './simple-text/input.html'; - -import simpleTextNormalized from './simple-text/normalized.html'; - -import simpleTextModel from './simple-text/model.html'; - -export const fixtures = { - input: { - simpleText - }, - normalized: { - simpleText: simpleTextNormalized - }, - model: { - simpleText: simpleTextModel - } -}; - -import simpleTextFirefox from './simple-text/input.firefox.html'; - -import simpleTextNormalizedFirefox from './simple-text/normalized.firefox.html'; - -export const browserFixtures = { - firefox: { - input: { - simpleText: simpleTextFirefox - }, - normalized: { - simpleText: simpleTextNormalizedFirefox - }, - model: { - simpleText: simpleTextModel - } - } -}; diff --git a/tests/_utils/fixtures.js b/tests/_utils/fixtures.js index ecda8cc..f41007a 100644 --- a/tests/_utils/fixtures.js +++ b/tests/_utils/fixtures.js @@ -9,7 +9,8 @@ import { fixtures as image, browserFixtures as imageBrowser } from '../_data/ima import { fixtures as link, browserFixtures as linkBrowser } from '../_data/link/index.js'; import { fixtures as list, browserFixtures as listBrowser } from '../_data/list/index.js'; import { fixtures as spacing, browserFixtures as spacingBrowser } from '../_data/spacing/index.js'; -import { fixtures as googleDocs, browserFixtures as googleDocsBrowser } from '../_data/paste-from-google-docs/index'; +import { fixtures as googleDocsBoldWrapper, browserFixtures as googleDocsBoldWrapperBrowser } + from '../_data/paste-from-google-docs/bold-wrapper/index'; // Generic fixtures. export const fixtures = { @@ -18,7 +19,7 @@ export const fixtures = { link, list, spacing, - 'google-docs': googleDocs + 'google-docs-bold-wrapper': googleDocsBoldWrapper }; // Browser specific fixtures. @@ -28,5 +29,5 @@ export const browserFixtures = { link: linkBrowser, list: listBrowser, spacing: spacingBrowser, - 'google-docs': googleDocsBrowser + 'google-docs-bold-wrapper': googleDocsBoldWrapperBrowser }; diff --git a/tests/_utils/utils.js b/tests/_utils/utils.js index 4baf5ca..9cddfad 100644 --- a/tests/_utils/utils.js +++ b/tests/_utils/utils.js @@ -17,6 +17,8 @@ import { stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-u import { fixtures, browserFixtures } from './fixtures'; +const htmlDataProcessor = new HtmlDataProcessor(); + /** * Mocks dataTransfer object which can be used for simulating paste. * @@ -135,8 +137,6 @@ function groupFixturesByBrowsers( browsers, fixturesGroup, skipBrowsers ) { // @param {Object} editorConfig Editor config with which test editor will be created. // @param {Array.} skip Array of fixtures names which tests should be skipped. function generateNormalizationTests( title, fixtures, editorConfig, skip ) { - const htmlDataProcessor = new HtmlDataProcessor(); - describe( title, () => { let editor; diff --git a/tests/data/integration.js b/tests/data/integration.js index 23d4b3a..388dc01 100644 --- a/tests/data/integration.js +++ b/tests/data/integration.js @@ -84,7 +84,7 @@ describe( 'PasteFromOffice/data - automatic', () => { } ); generateTests( { - input: 'google-docs', + input: 'google-docs-bold-wrapper', type: 'integration', browsers, editorConfig: { diff --git a/tests/data/normalization.js b/tests/data/normalization.js index b76bca9..728b932 100644 --- a/tests/data/normalization.js +++ b/tests/data/normalization.js @@ -51,7 +51,7 @@ describe( 'PasteFromOffice/data - automatic', () => { } ); generateTests( { - input: 'google-docs', + input: 'google-docs-bold-wrapper', type: 'normalization', browsers, editorConfig From 4b05a6743567f2894fc2660b265a7470891dc58d Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 19 Jul 2019 15:16:34 +0200 Subject: [PATCH 11/48] Introduce concept of normalizers. --- src/contentnormalizer.js | 70 ++++++++++++++++++++++++++++++++++++++ src/pastefromoffice.js | 72 ++++++++++++++++++++++++++++++---------- 2 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 src/contentnormalizer.js diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js new file mode 100644 index 0000000..2fc9687 --- /dev/null +++ b/src/contentnormalizer.js @@ -0,0 +1,70 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/contentnormalizer + */ + +export default class ContentNormalizer { + constructor( { activationTrigger } ) { + this.data = null; + + this.activationTrigger = activationTrigger; + + this._active = false; + this._filters = new Set(); + this._fullContentFilters = new Set(); + } + + get isActive() { + return this._active; + } + + addData( data ) { + const html = data.dataTransfer.getData( 'text/html' ); + + if ( html && this.activationTrigger( html ) ) { + this.data = data; + this._active = true; + + if ( this.data.isTransformedWithPasteFromOffice === undefined ) { + this.data.isTransformedWithPasteFromOffice = false; + } + } else { + this.data = undefined; + this._active = false; + } + + return this; + } + + addFilter( filterDefinition ) { + if ( filterDefinition.fullContent ) { + this._fullContentFilters.add( filterDefinition ); + } else { + this._filters.add( filterDefinition ); + } + } + + filter() { + if ( this.data && !this.data.isTransformedWithPasteFromOffice ) { + this._applyFullContentFilters(); + + this.data.isTransformedWithPasteFromOffice = true; + } + + return this; + } + + _applyFullContentFilters() { + if ( !this._fullContentFilters.size ) { + return; + } + + for ( const filter of this._fullContentFilters ) { + filter.exec( this.data ); + } + } +} diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index c2139f0..57aac03 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -13,6 +13,8 @@ import { parseHtml } from './filters/parse'; import { transformListItemLikeElementsIntoLists } from './filters/list'; import { replaceImagesSourceWithBase64 } from './filters/image'; import { removeBoldTagWrapper } from './filters/common'; +import ContentNormalizer from './contentnormalizer'; +import Collection from '@ckeditor/ckeditor5-utils/src/collection'; /** * The Paste from Office plugin. @@ -25,6 +27,18 @@ import { removeBoldTagWrapper } from './filters/common'; * @extends module:core/plugin~Plugin */ export default class PasteFromOffice extends Plugin { + /** + * @inheritDoc + */ + constructor( editor ) { + super( editor ); + + this._normalizers = new Collection(); + + this._normalizers.add( this._getWordNormalizer() ); + this._normalizers.add( this._getGoogleDocsNormalizer() ); + } + /** * @inheritDoc */ @@ -41,11 +55,45 @@ export default class PasteFromOffice extends Plugin { this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', - PasteFromOffice._inputTransformationListener, + this._inputTransformationListener.bind( this ), { priority: 'high' } ); } + _getWordNormalizer() { + const wordNormalizer = new ContentNormalizer( { + activationTrigger: contentString => + //i.test( contentString ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) + } ); + + wordNormalizer.addFilter( { + fullContent: true, + exec: data => { + const html = data.dataTransfer.getData( 'text/html' ); + + data.content = PasteFromOffice._normalizeWordInput( html, data.dataTransfer ); + } + } ); + + return wordNormalizer; + } + + _getGoogleDocsNormalizer() { + const googleDocsNormalizer = new ContentNormalizer( { + activationTrigger: contentString => /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) + } ); + + googleDocsNormalizer.addFilter( { + fullContent: true, + exec: data => { + removeBoldTagWrapper( data.content ); + } + } ); + + return googleDocsNormalizer; + } + /** * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation `inputTransformation` event}. * Detects if content comes from a recognized source and normalize it. @@ -56,23 +104,13 @@ export default class PasteFromOffice extends Plugin { * @param {module:utils/eventinfo~EventInfo} evt * @param {Object} data same structure like {@link module:clipboard/clipboard~Clipboard#event:inputTransformation input transformation} */ - static _inputTransformationListener( evt, data ) { - const html = data.dataTransfer.getData( 'text/html' ); - - if ( data.pasteFromOfficeProcessed !== true ) { - switch ( PasteFromOffice._getInputType( html ) ) { - case 'msword': - data.content = PasteFromOffice._normalizeWordInput( html, data.dataTransfer ); - break; - case 'gdocs': - data.content = PasteFromOffice._normalizeGoogleDocsInput( data.content ); - break; - default: - break; - } + _inputTransformationListener( evt, data ) { + for ( const normalizer of this._normalizers ) { + normalizer.addData( data ); - // Set the flag so if `inputTransformation` is re-fired, PFO will not process it again (#44). - data.pasteFromOfficeProcessed = true; + if ( normalizer.isActive ) { + normalizer.filter(); + } } } From d4805f6fd43443a5297e65894d6016a3d8b85edb Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 19 Jul 2019 16:04:24 +0200 Subject: [PATCH 12/48] Implement normalizers instances for google docs and ms word and extract them to separate files. Clean up in contentnormalizer api. --- src/contentnormalizer.js | 4 +- src/normalizers/googledocs.js | 26 +++++++ src/normalizers/msword.js | 49 ++++++++++++ src/pastefromoffice.js | 138 ++++------------------------------ 4 files changed, 91 insertions(+), 126 deletions(-) create mode 100644 src/normalizers/googledocs.js create mode 100644 src/normalizers/msword.js diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 2fc9687..34435d8 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -22,7 +22,7 @@ export default class ContentNormalizer { return this._active; } - addData( data ) { + setInputData( data ) { const html = data.dataTransfer.getData( 'text/html' ); if ( html && this.activationTrigger( html ) ) { @@ -48,7 +48,7 @@ export default class ContentNormalizer { } } - filter() { + exec() { if ( this.data && !this.data.isTransformedWithPasteFromOffice ) { this._applyFullContentFilters(); diff --git a/src/normalizers/googledocs.js b/src/normalizers/googledocs.js new file mode 100644 index 0000000..864cb39 --- /dev/null +++ b/src/normalizers/googledocs.js @@ -0,0 +1,26 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/normalizer + */ + +import ContentNormalizer from '../contentnormalizer'; +import { removeBoldTagWrapper } from '../filters/common'; + +export const googleDocsNormalizer = ( () => { + const normalizer = new ContentNormalizer( { + activationTrigger: contentString => /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) + } ); + + normalizer.addFilter( { + fullContent: true, + exec: data => { + removeBoldTagWrapper( data.content ); + } + } ); + + return normalizer; +} )(); diff --git a/src/normalizers/msword.js b/src/normalizers/msword.js new file mode 100644 index 0000000..bce51ff --- /dev/null +++ b/src/normalizers/msword.js @@ -0,0 +1,49 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/normalizer + */ + +import ContentNormalizer from '../contentnormalizer'; +import { parseHtml } from '../filters/parse'; +import { transformListItemLikeElementsIntoLists } from '../filters/list'; +import { replaceImagesSourceWithBase64 } from '../filters/image'; + +export const mswordNormalizer = ( () => { + const normalizer = new ContentNormalizer( { + activationTrigger: contentString => + //i.test( contentString ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) + } ); + + normalizer.addFilter( { + fullContent: true, + exec: data => { + const html = data.dataTransfer.getData( 'text/html' ); + + data.content = _normalizeWordInput( html, data.dataTransfer ); + } + } ); + + return normalizer; +} )(); + +// +// Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. +// +// @private +// @param {String} input Word input. +// @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. +// @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. +// +function _normalizeWordInput( input, dataTransfer ) { + const { body, stylesString } = parseHtml( input ); + + transformListItemLikeElementsIntoLists( body, stylesString ); + replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); + + return body; +} diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 57aac03..6fe7bfc 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,12 +9,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import { parseHtml } from './filters/parse'; -import { transformListItemLikeElementsIntoLists } from './filters/list'; -import { replaceImagesSourceWithBase64 } from './filters/image'; -import { removeBoldTagWrapper } from './filters/common'; -import ContentNormalizer from './contentnormalizer'; -import Collection from '@ckeditor/ckeditor5-utils/src/collection'; +import { googleDocsNormalizer } from './normalizers/googledocs'; +import { mswordNormalizer } from './normalizers/msword'; /** * The Paste from Office plugin. @@ -33,10 +29,10 @@ export default class PasteFromOffice extends Plugin { constructor( editor ) { super( editor ); - this._normalizers = new Collection(); + this._normalizers = new Set(); - this._normalizers.add( this._getWordNormalizer() ); - this._normalizers.add( this._getGoogleDocsNormalizer() ); + this._normalizers.add( mswordNormalizer ); + this._normalizers.add( googleDocsNormalizer ); } /** @@ -55,122 +51,16 @@ export default class PasteFromOffice extends Plugin { this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', - this._inputTransformationListener.bind( this ), + ( evt, data ) => { + for ( const normalizer of this._normalizers ) { + normalizer.setInputData( data ); + + if ( normalizer.isActive ) { + normalizer.exec(); + } + } + }, { priority: 'high' } ); } - - _getWordNormalizer() { - const wordNormalizer = new ContentNormalizer( { - activationTrigger: contentString => - //i.test( contentString ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) - } ); - - wordNormalizer.addFilter( { - fullContent: true, - exec: data => { - const html = data.dataTransfer.getData( 'text/html' ); - - data.content = PasteFromOffice._normalizeWordInput( html, data.dataTransfer ); - } - } ); - - return wordNormalizer; - } - - _getGoogleDocsNormalizer() { - const googleDocsNormalizer = new ContentNormalizer( { - activationTrigger: contentString => /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) - } ); - - googleDocsNormalizer.addFilter( { - fullContent: true, - exec: data => { - removeBoldTagWrapper( data.content ); - } - } ); - - return googleDocsNormalizer; - } - - /** - * Listener fired during {@link module:clipboard/clipboard~Clipboard#event:inputTransformation `inputTransformation` event}. - * Detects if content comes from a recognized source and normalize it. - * - * **Note**: this function was exposed mainly for testing purposes and should not be called directly. - * - * @private - * @param {module:utils/eventinfo~EventInfo} evt - * @param {Object} data same structure like {@link module:clipboard/clipboard~Clipboard#event:inputTransformation input transformation} - */ - _inputTransformationListener( evt, data ) { - for ( const normalizer of this._normalizers ) { - normalizer.addData( data ); - - if ( normalizer.isActive ) { - normalizer.filter(); - } - } - } - - /** - * Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. - * - * **Note**: this function is exposed mainly for testing purposes and should not be called directly. - * - * @private - * @param {String} input Word input. - * @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. - * @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. - */ - static _normalizeWordInput( input, dataTransfer ) { - const { body, stylesString } = parseHtml( input ); - - transformListItemLikeElementsIntoLists( body, stylesString ); - replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); - - return body; - } - - /** - * Normalizes input pasted from Google Docs to format suitable for editor {@link module:engine/model/model~Model}. - * - * **Note**: this function is exposed mainly for testing purposes and should not be called directly. - * - * @private - * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment normalized clipboard data - * @returns {module:engine/view/documentfragment~DocumentFragment} document fragment normalized with set of filters dedicated - * for Google Docs - */ - static _normalizeGoogleDocsInput( documentFragment ) { - return removeBoldTagWrapper( documentFragment ); - } - - /** - * Determines if given paste data came from the specific office-like application. - * Currently recognized are: - * * `'msword'` for Microsoft Words desktop app - * * `'gdocs'` for Google Docs online app - * - * **Note**: this function is exposed mainly for testing purposes and should not be called directly. - * - * @private - * @param {String} html the `text/html` string from data transfer - * @return {String|null} name of source app of an html data or null - */ - static _getInputType( html ) { - if ( - //i.test( html ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( html ) - ) { - // Microsoft Word detection - return 'msword'; - } else if ( /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( html ) ) { - // Google Docs detection - return 'gdocs'; - } - - return null; - } } From ca4b62fd68019e2f94b3f0927dac7b2f7d7694af Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 22 Jul 2019 12:17:00 +0200 Subject: [PATCH 13/48] Fix automatic test to use clipbaord event instead of run function manually. --- tests/_utils/utils.js | 23 +++++++++++++---------- tests/pastefromoffice.js | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/_utils/utils.js b/tests/_utils/utils.js index 9cddfad..8fad203 100644 --- a/tests/_utils/utils.js +++ b/tests/_utils/utils.js @@ -8,7 +8,6 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; -import PasteFromOffice from '../../src/pastefromoffice'; import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; import normalizeClipboardData from '@ckeditor/ckeditor5-clipboard/src/utils/normalizeclipboarddata'; import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml'; @@ -155,18 +154,22 @@ function generateNormalizationTests( title, fixtures, editorConfig, skip ) { for ( const name of Object.keys( fixtures.input ) ) { ( skip.indexOf( name ) !== -1 ? it.skip : it )( name, () => { // Simulate data from Clipboard event - const data = { - content: htmlDataProcessor.toView( normalizeClipboardData( fixtures.input[ name ] ) ), - dataTransfer: createDataTransfer( { - 'text/html': fixtures.input[ name ], - 'text/rtf': fixtures.inputRtf && fixtures.inputRtf[ name ] - } ) - }; + const clipboardPlugin = editor.plugins.get( 'Clipboard' ); + const content = htmlDataProcessor.toView( normalizeClipboardData( fixtures.input[ name ] ) ); + const dataTransfer = createDataTransfer( { + 'text/html': fixtures.input[ name ], + 'text/rtf': fixtures.inputRtf && fixtures.inputRtf[ name ] + } ); + + // data.content might be completely overwritten with a new object, so we need obtain final result for comparison. + clipboardPlugin.on( 'inputTransformation', ( evt, data ) => { + evt.return = data.content; + }, { priority: 'lowest' } ); - PasteFromOffice._inputTransformationListener( null, data ); + const transformedContent = clipboardPlugin.fire( 'inputTransformation', { content, dataTransfer } ); expectNormalized( - data.content, + transformedContent, fixtures.normalized[ name ] ); } ); diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 290d34a..20df7fa 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -12,7 +12,7 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/html import PasteFromOffice from '../src/pastefromoffice'; import { createDataTransfer } from './_utils/utils'; -describe( 'PasteFromOffice', () => { +describe.skip( 'PasteFromOffice', () => { let editor, content, normalizeSpy; testUtils.createSinonSandbox(); From ffd320b1553cf44696674435f537315c208d4160 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 22 Jul 2019 12:44:15 +0200 Subject: [PATCH 14/48] Add requires to te plugin. --- src/pastefromoffice.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 6fe7bfc..a8afb23 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -11,6 +11,7 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import { googleDocsNormalizer } from './normalizers/googledocs'; import { mswordNormalizer } from './normalizers/msword'; +import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; /** * The Paste from Office plugin. @@ -42,6 +43,13 @@ export default class PasteFromOffice extends Plugin { return 'PasteFromOffice'; } + /** + * @inheritDoc + */ + static get requires() { + return [ Clipboard ]; + } + /** * @inheritDoc */ From f7e6dbd7ee76a2eeab00832638f68c2370a3a820 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 22 Jul 2019 13:35:57 +0200 Subject: [PATCH 15/48] Provide different test set for paste from office class. --- tests/pastefromoffice.js | 170 +++++++-------------------------------- 1 file changed, 27 insertions(+), 143 deletions(-) diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 20df7fa..742f1a9 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -3,158 +3,42 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ +import PasteFromOffice from '../src/pastefromoffice'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; -import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; -import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; -import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; - -import PasteFromOffice from '../src/pastefromoffice'; -import { createDataTransfer } from './_utils/utils'; - -describe.skip( 'PasteFromOffice', () => { - let editor, content, normalizeSpy; - - testUtils.createSinonSandbox(); - - describe( '_normalizeWordInput()', () => { - before( () => { - content = new DocumentFragment(); - } ); - - beforeEach( () => { - return VirtualTestEditor - .create( { - plugins: [ Clipboard, PasteFromOffice ] - } ) - .then( newEditor => { - editor = newEditor; - normalizeSpy = testUtils.sinon.spy( PasteFromOffice, '_normalizeWordInput' ); - } ); - } ); - - afterEach( () => { - editor.destroy(); - } ); - - it( 'runs normalizations if Word meta tag detected #1', () => { - const dataTransfer = createDataTransfer( { - 'text/html': '' - } ); - - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - - expect( normalizeSpy.calledOnce ).to.true; - } ); - - it( 'runs normalizations if Word meta tag detected #2', () => { - const dataTransfer = createDataTransfer( { - 'text/html': '' - } ); - - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - - expect( normalizeSpy.calledOnce ).to.true; - } ); - - it( 'does not normalize the content without Word meta tag', () => { - const dataTransfer = createDataTransfer( { - 'text/html': '' - } ); - - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - - expect( normalizeSpy.called ).to.false; - } ); - - it( 'does not process content many times for the same `inputTransformation` event', () => { - const clipboard = editor.plugins.get( 'Clipboard' ); - - const dataTransfer = createDataTransfer( { - 'text/html': '' +import ContentNormalizer from '../src/contentnormalizer'; + +describe( 'PasteFromOffice', () => { + let editor, pasteFromOffice; + beforeEach( () => { + return VirtualTestEditor.create( { + plugins: [ PasteFromOffice ] + } ) + .then( _editor => { + editor = _editor; + pasteFromOffice = editor.plugins.get( 'PasteFromOffice' ); } ); - - let eventRefired = false; - clipboard.on( 'inputTransformation', ( evt, data ) => { - if ( !eventRefired ) { - eventRefired = true; - - evt.stop(); - - clipboard.fire( 'inputTransformation', data ); - } - - expect( data.pasteFromOfficeProcessed ).to.true; - expect( normalizeSpy.calledOnce ).to.true; - }, { priority: 'low' } ); - - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); - - expect( normalizeSpy.calledOnce ).to.true; - } ); } ); - describe( '_normalizeGoogleDocsInput()', () => { - before( () => { - content = new DocumentFragment(); - } ); - - beforeEach( () => { - return VirtualTestEditor - .create( { - plugins: [ Clipboard, PasteFromOffice ] - } ) - .then( newEditor => { - editor = newEditor; - normalizeSpy = testUtils.sinon.spy( PasteFromOffice, '_normalizeGoogleDocsInput' ); - } ); - } ); - - afterEach( () => { - editor.destroy(); - } ); - - it( 'runs normalizations if Google docs meta tag detected #1', () => { - const fakeClipboardData = ''; - const dataTransfer = createDataTransfer( { - 'text/html': fakeClipboardData - } ); - const htmlDataProcessor = new HtmlDataProcessor(); - const content = htmlDataProcessor.toView( fakeClipboardData ); + it( 'is Paste from Office', () => { + expect( pasteFromOffice ).to.be.instanceOf( PasteFromOffice ); + } ); - editor.plugins.get( 'Clipboard' ).fire( 'inputTransformation', { content, dataTransfer } ); + it( 'should have static name', () => { + expect( PasteFromOffice.pluginName ).to.equal( 'PasteFromOffice' ); + } ); - expect( normalizeSpy.calledOnce ).to.true; - } ); + it( 'should load Clipboard plugin', () => { + expect( editor.plugins.get( Clipboard ) ).to.be.instanceOf( Clipboard ); } ); - describe( '_getInputType()', () => { - [ - { - input: '', - output: 'msword' - }, - { - input: '', - output: 'msword' - }, - { - input: '', - output: null - }, - { - input: '', - output: 'gdocs' - } - ].forEach( ( value, index ) => { - it( `should return proper input type for test case: #${ index }.`, () => { - if ( value.output ) { - expect( PasteFromOffice._getInputType( value.input ) ).to.equal( value.output ); - } else { - expect( PasteFromOffice._getInputType( value.input ) ).to.be.null; - } + describe( 'constructor()', () => { + it( 'should initialize 2 normalizers', () => { + expect( pasteFromOffice._normalizers ).to.be.a( 'set' ); + expect( pasteFromOffice._normalizers.size ).to.equal( 2 ); + + pasteFromOffice._normalizers.forEach( value => { + expect( value ).to.be.instanceOf( ContentNormalizer ); } ); } ); } ); From a6a6ad3d74f6d31625ba5acc20f62c1265975fa1 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 22 Jul 2019 16:56:25 +0200 Subject: [PATCH 16/48] Provide small improvements in normalizer. Add unit test covering normalizers functionalities. --- src/contentnormalizer.js | 25 ++--- tests/contentnormalizer.js | 224 +++++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 15 deletions(-) create mode 100644 tests/contentnormalizer.js diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 34435d8..5df961e 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -13,28 +13,23 @@ export default class ContentNormalizer { this.activationTrigger = activationTrigger; - this._active = false; + this.isActive = false; this._filters = new Set(); this._fullContentFilters = new Set(); } - get isActive() { - return this._active; - } - setInputData( data ) { const html = data.dataTransfer.getData( 'text/html' ); + const dataReadFirstTime = data.isTransformedWithPasteFromOffice === undefined; + const hasHtmlData = !!html; - if ( html && this.activationTrigger( html ) ) { + if ( hasHtmlData && dataReadFirstTime && this.activationTrigger( html ) ) { this.data = data; - this._active = true; - - if ( this.data.isTransformedWithPasteFromOffice === undefined ) { - this.data.isTransformedWithPasteFromOffice = false; - } + this.isActive = true; + this.data.isTransformedWithPasteFromOffice = false; } else { - this.data = undefined; - this._active = false; + this.data = null; + this.isActive = false; } return this; @@ -51,10 +46,10 @@ export default class ContentNormalizer { exec() { if ( this.data && !this.data.isTransformedWithPasteFromOffice ) { this._applyFullContentFilters(); - - this.data.isTransformedWithPasteFromOffice = true; } + this.data.isTransformedWithPasteFromOffice = true; + return this; } diff --git a/tests/contentnormalizer.js b/tests/contentnormalizer.js new file mode 100644 index 0000000..5253e2c --- /dev/null +++ b/tests/contentnormalizer.js @@ -0,0 +1,224 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; +import ContentNormalizer from '../src/contentnormalizer'; + +describe( 'ContentNormalizer', () => { + let normalizer, sinonTrigger; + const templateData = { + dataTransfer: { + getData: () => 'test data' + } + }; + + testUtils.createSinonSandbox(); + + beforeEach( () => { + sinonTrigger = sinon.fake.returns( true ); + + normalizer = new ContentNormalizer( { + activationTrigger: sinonTrigger + } ); + } ); + + describe( 'constructor()', () => { + it( 'should be not active at start', () => { + expect( normalizer.isActive ).to.be.false; + } ); + + it( 'should not have assigned data', () => { + expect( normalizer.data ).to.be.null; + } ); + + it( 'should have assigned activation trigger', () => { + expect( normalizer.activationTrigger ).to.be.a( 'function' ); + expect( normalizer.activationTrigger ).to.equal( sinonTrigger ); + } ); + + it( 'should initialize sets for filters', () => { + expect( normalizer._filters ).to.be.a( 'set' ); + expect( normalizer._fullContentFilters ).to.be.a( 'set' ); + } ); + } ); + + describe( 'setInputData()', () => { + let data; + + beforeEach( () => { + data = Object.assign( {}, templateData ); + } ); + + describe( 'trigger activated', () => { + beforeEach( () => { + normalizer.setInputData( data ); + } ); + + it( 'should set data', () => { + expect( normalizer.data ).to.equal = data; + } ); + + it( 'should check if activates normalizer', () => { + sinon.assert.calledOnce( sinonTrigger ); + sinon.assert.calledWith( sinonTrigger, 'test data' ); + + expect( normalizer.isActive ).to.be.true; + } ); + + it( 'should add flag to data processed by paste from office plugin', () => { + expect( data.isTransformedWithPasteFromOffice ).to.be.false; + } ); + } ); + + describe( 'trigger not activated', () => { + beforeEach( () => { + sinonTrigger = sinon.fake.returns( false ); + + normalizer = new ContentNormalizer( { + activationTrigger: sinonTrigger + } ); + + normalizer.setInputData( data ); + } ); + + it( 'should not be active', () => { + sinon.assert.calledOnce( sinonTrigger ); + sinon.assert.calledWith( sinonTrigger, 'test data' ); + + expect( normalizer.isActive ).to.be.false; + } ); + + it( 'should not keep reference to data when is not active', () => { + expect( normalizer.data ).to.be.null; + } ); + + it( 'should not add flag to not processed data', () => { + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + } ); + } ); + + describe( 'already processed data', () => { + beforeEach( () => { + data.isTransformedWithPasteFromOffice = true; + + normalizer.addFilter( { + fullContent: true, + // eslint-disable-next-line no-unused-vars + exec: d => { d = {}; } + } ); + } ); + + it( 'should not change data', () => { + normalizer.setInputData( data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + expect( data ).to.deep.include( templateData ); + } ); + } ); + } ); + + describe( 'addFilter()', () => { + let filter; + describe( 'fullContentFilters', () => { + beforeEach( () => { + filter = { + fullContent: true, + exec: () => {} + }; + + normalizer.addFilter( filter ); + } ); + + it( 'should add filter to fullContentFilters set', () => { + expect( normalizer._fullContentFilters.size ).to.equal( 1 ); + expect( normalizer._filters.size ).to.equal( 0 ); + + const firstFilter = [ ...normalizer._fullContentFilters ][ 0 ]; + expect( firstFilter ).to.equal( filter ); + } ); + } ); + } ); + + describe( 'exec()', () => { + let data, filter; + beforeEach( () => { + data = Object.assign( {}, templateData ); + filter = { + fullContent: true, + exec: data => { + data.content = 'Foo bar baz.'; + } + }; + + normalizer.addFilter( filter ); + } ); + + it( 'should apply filter#exec to data', () => { + normalizer.setInputData( data ); + + expect( data.content ).to.be.undefined; + + normalizer.exec(); + + expect( data.content ).to.equal( 'Foo bar baz.' ); + } ); + + it( 'should mark data as processed with paste from office', () => { + normalizer.setInputData( data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.false; + + normalizer.exec(); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + } ); + + describe( 'already processed data', () => { + let execFake; + beforeEach( () => { + execFake = sinon.fake(); + normalizer = new ContentNormalizer( { activationTrigger: () => true } ); + + normalizer.addFilter( { + fullContent: true, + exec: execFake + } ); + } ); + + it( 'should not apply filter on already processed data', () => { + normalizer.setInputData( data ); + + sinon.assert.notCalled( execFake ); + expect( data.isTransformedWithPasteFromOffice ).to.be.false; + + normalizer.exec(); + sinon.assert.calledOnce( execFake ); + sinon.assert.calledWith( execFake, data ); + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + + normalizer.exec(); + sinon.assert.calledOnce( execFake ); + } ); + } ); + + describe( 'normalizer without filter', () => { + beforeEach( () => { + normalizer = new ContentNormalizer( { activationTrigger: () => true } ); + } ); + + it( 'should do nothing with data', () => { + normalizer.setInputData( data ); + + expect( data ).to.deep.include( templateData ); + expect( data.isTransformedWithPasteFromOffice ).to.be.false; + + normalizer.exec(); + + expect( data ).to.deep.include( templateData ); + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + } ); + } ); + } ); +} ); From aa541e4d8a4d0d0d60dc32095ac438d8844a2542 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 23 Jul 2019 10:56:35 +0200 Subject: [PATCH 17/48] Add tests for msword normalizer, improve content normalizer class. --- src/contentnormalizer.js | 23 ++++++--- tests/contentnormalizer.js | 7 +-- tests/normalizers/msword.js | 98 +++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 tests/normalizers/msword.js diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 5df961e..42b8275 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -9,17 +9,18 @@ export default class ContentNormalizer { constructor( { activationTrigger } ) { - this.data = null; - this.activationTrigger = activationTrigger; - this.isActive = false; + this._reset(); + this._filters = new Set(); this._fullContentFilters = new Set(); } setInputData( data ) { - const html = data.dataTransfer.getData( 'text/html' ); + this._reset(); + + const html = data.dataTransfer && data.dataTransfer.getData( 'text/html' ); const dataReadFirstTime = data.isTransformedWithPasteFromOffice === undefined; const hasHtmlData = !!html; @@ -27,9 +28,6 @@ export default class ContentNormalizer { this.data = data; this.isActive = true; this.data.isTransformedWithPasteFromOffice = false; - } else { - this.data = null; - this.isActive = false; } return this; @@ -44,7 +42,11 @@ export default class ContentNormalizer { } exec() { - if ( this.data && !this.data.isTransformedWithPasteFromOffice ) { + if ( !this.isActive ) { + return; + } + + if ( !this.data.isTransformedWithPasteFromOffice ) { this._applyFullContentFilters(); } @@ -53,6 +55,11 @@ export default class ContentNormalizer { return this; } + _reset() { + this.data = null; + this.isActive = false; + } + _applyFullContentFilters() { if ( !this._fullContentFilters.size ) { return; diff --git a/tests/contentnormalizer.js b/tests/contentnormalizer.js index 5253e2c..17c6e55 100644 --- a/tests/contentnormalizer.js +++ b/tests/contentnormalizer.js @@ -5,13 +5,14 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import ContentNormalizer from '../src/contentnormalizer'; +import { createDataTransfer } from './_utils/utils'; describe( 'ContentNormalizer', () => { let normalizer, sinonTrigger; const templateData = { - dataTransfer: { - getData: () => 'test data' - } + dataTransfer: createDataTransfer( { + 'text/html': 'test data' + } ) }; testUtils.createSinonSandbox(); diff --git a/tests/normalizers/msword.js b/tests/normalizers/msword.js new file mode 100644 index 0000000..faf3652 --- /dev/null +++ b/tests/normalizers/msword.js @@ -0,0 +1,98 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import { mswordNormalizer as normalizer } from '../../src/normalizers/msword'; +import ContentNormalizer from '../../src/contentnormalizer'; +import { createDataTransfer } from '../_utils/utils'; +import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; + +// Functionality of the msword normalizer is tested with autogenerated normalization tests. +describe( 'PasteFromOffice/normalizers/msword', () => { + afterEach( () => { + normalizer.setInputData( {} ); + } ); + + it( 'should be instance of content normalizers', () => { + expect( normalizer ).to.be.instanceOf( ContentNormalizer ); + } ); + + it( 'should mark data as processed', () => { + const data = { + dataTransfer: createDataTransfer( { + 'text/html': '' + } ) + }; + + normalizer.setInputData( data ).exec(); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + } ); + + it( 'should not mark non-word data as processed', () => { + const data = { + dataTransfer: createDataTransfer( { 'text/html': 'foo bar' } ) + }; + + normalizer.setInputData( data ).exec(); + + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + } ); + + it( 'outputs view#documentFragment', () => { + const data = { + dataTransfer: createDataTransfer( { + 'text/html': '

Foo bar

' + } ) + }; + + normalizer.setInputData( data ).exec(); + + expect( data.content ).to.be.instanceOf( DocumentFragment ); + } ); + + describe( 'activation trigger', () => { + describe( 'correct markup', () => { + [ + { + 'text/html': '' + }, + { + 'text/html': '' + } + ].forEach( ( data, index ) => { + it( `should be active for markup #${ index }`, () => { + expect( normalizer.isActive ).to.be.false; + + normalizer.setInputData( { + dataTransfer: createDataTransfer( data ) + } ); + + expect( normalizer.isActive ).to.be.true; + } ); + } ); + } ); + + describe( 'wrong markup', () => { + [ + { + 'text/html': '' + }, + { + 'text/html': '

' + } + ].forEach( ( data, index ) => { + it( `should be not active for wrong markup #${ index }`, () => { + expect( normalizer.isActive ).to.be.false; + + normalizer.setInputData( { + dataTransfer: createDataTransfer( data ) + } ); + + expect( normalizer.isActive ).to.be.false; + } ); + } ); + } ); + } ); +} ); From 24a349c4f0540f1dad1ed1658c4bf7cc321aca9e Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 23 Jul 2019 11:30:50 +0200 Subject: [PATCH 18/48] Add test for google docs normalizer. --- tests/normalizers/googledocs.js | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 tests/normalizers/googledocs.js diff --git a/tests/normalizers/googledocs.js b/tests/normalizers/googledocs.js new file mode 100644 index 0000000..e1cd3b4 --- /dev/null +++ b/tests/normalizers/googledocs.js @@ -0,0 +1,107 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import { googleDocsNormalizer as normalizer } from '../../src/normalizers/googledocs'; +import ContentNormalizer from '../../src/contentnormalizer'; +import { createDataTransfer } from '../_utils/utils'; +import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; + +// Functionality of the google docs normalizer is tested with autogenerated normalization tests. +describe( 'PasteFromOffice/normalizers/googledocs', () => { + const htmlDataProcessor = new HtmlDataProcessor(); + + afterEach( () => { + normalizer.setInputData( {} ); + } ); + + it( 'should be instance of content normalizers', () => { + expect( normalizer ).to.be.instanceOf( ContentNormalizer ); + } ); + + it( 'should mark data as processed', () => { + const gDocs = '

'; + const data = { + dataTransfer: createDataTransfer( { + 'text/html': gDocs + } ), + content: htmlDataProcessor.toView( gDocs ) + }; + + normalizer.setInputData( data ).exec(); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + } ); + + it( 'should not mark non-google-docs data as processed', () => { + const data = { + dataTransfer: createDataTransfer( { 'text/html': 'foo bar' } ) + }; + + normalizer.setInputData( data ).exec(); + + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + } ); + + it( 'outputs view#documentFragment', () => { + const gDocs = '

Foo bar

'; + const data = { + dataTransfer: createDataTransfer( { + 'text/html': gDocs + } ), + content: htmlDataProcessor.toView( gDocs ) + }; + + normalizer.setInputData( data ).exec(); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + expect( data.content ).to.be.instanceOf( DocumentFragment ); + } ); + + describe( 'activation trigger', () => { + describe( 'correct markup', () => { + [ + { + 'text/html': '

Foo bar

' + }, + { + // eslint-disable-next-line max-len + 'text/html': '' + } + ].forEach( ( data, index ) => { + it( `should be active for markup #${ index }`, () => { + expect( normalizer.isActive ).to.be.false; + + normalizer.setInputData( { + dataTransfer: createDataTransfer( data ) + } ); + + expect( normalizer.isActive ).to.be.true; + } ); + } ); + } ); + + describe( 'wrong markup', () => { + [ + { + 'text/html': '

Hello world

' + }, + { + 'text/html': '' + } + ].forEach( ( data, index ) => { + it( `should be not active for wrong markup #${ index }`, () => { + expect( normalizer.isActive ).to.be.false; + + normalizer.setInputData( { + dataTransfer: createDataTransfer( data ) + } ); + + expect( normalizer.isActive ).to.be.false; + } ); + } ); + } ); + } ); +} ); From a907b162eae19d28c401e5896b9feecaa4080cf4 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Tue, 23 Jul 2019 16:59:25 +0200 Subject: [PATCH 19/48] Simplify content normalizers to required functionality. --- src/contentnormalizer.js | 49 ++++++----------------------------- src/normalizers/googledocs.js | 7 ++--- src/normalizers/msword.js | 9 +++---- src/pastefromoffice.js | 6 +---- tests/contentnormalizer.js | 2 +- 5 files changed, 15 insertions(+), 58 deletions(-) diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 42b8275..237e095 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -11,62 +11,29 @@ export default class ContentNormalizer { constructor( { activationTrigger } ) { this.activationTrigger = activationTrigger; - this._reset(); - this._filters = new Set(); - this._fullContentFilters = new Set(); } - setInputData( data ) { - this._reset(); - + transform( data ) { const html = data.dataTransfer && data.dataTransfer.getData( 'text/html' ); const dataReadFirstTime = data.isTransformedWithPasteFromOffice === undefined; const hasHtmlData = !!html; if ( hasHtmlData && dataReadFirstTime && this.activationTrigger( html ) ) { - this.data = data; - this.isActive = true; - this.data.isTransformedWithPasteFromOffice = false; + this._applyFilters( data ); + data.isTransformedWithPasteFromOffice = true; } return this; } - addFilter( filterDefinition ) { - if ( filterDefinition.fullContent ) { - this._fullContentFilters.add( filterDefinition ); - } else { - this._filters.add( filterDefinition ); - } + addFilter( filterFn ) { + this._filters.add( filterFn ); } - exec() { - if ( !this.isActive ) { - return; - } - - if ( !this.data.isTransformedWithPasteFromOffice ) { - this._applyFullContentFilters(); - } - - this.data.isTransformedWithPasteFromOffice = true; - - return this; - } - - _reset() { - this.data = null; - this.isActive = false; - } - - _applyFullContentFilters() { - if ( !this._fullContentFilters.size ) { - return; - } - - for ( const filter of this._fullContentFilters ) { - filter.exec( this.data ); + _applyFilters( data ) { + for ( const filter of this._filters ) { + filter( data ); } } } diff --git a/src/normalizers/googledocs.js b/src/normalizers/googledocs.js index 864cb39..fd66fbb 100644 --- a/src/normalizers/googledocs.js +++ b/src/normalizers/googledocs.js @@ -15,11 +15,8 @@ export const googleDocsNormalizer = ( () => { activationTrigger: contentString => /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) } ); - normalizer.addFilter( { - fullContent: true, - exec: data => { - removeBoldTagWrapper( data.content ); - } + normalizer.addFilter( data => { + removeBoldTagWrapper( data.content ); } ); return normalizer; diff --git a/src/normalizers/msword.js b/src/normalizers/msword.js index bce51ff..3498d17 100644 --- a/src/normalizers/msword.js +++ b/src/normalizers/msword.js @@ -19,13 +19,10 @@ export const mswordNormalizer = ( () => { /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) } ); - normalizer.addFilter( { - fullContent: true, - exec: data => { - const html = data.dataTransfer.getData( 'text/html' ); + normalizer.addFilter( data => { + const html = data.dataTransfer.getData( 'text/html' ); - data.content = _normalizeWordInput( html, data.dataTransfer ); - } + data.content = _normalizeWordInput( html, data.dataTransfer ); } ); return normalizer; diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index a8afb23..59761b3 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -61,11 +61,7 @@ export default class PasteFromOffice extends Plugin { 'inputTransformation', ( evt, data ) => { for ( const normalizer of this._normalizers ) { - normalizer.setInputData( data ); - - if ( normalizer.isActive ) { - normalizer.exec(); - } + normalizer.transform( data ); } }, { priority: 'high' } diff --git a/tests/contentnormalizer.js b/tests/contentnormalizer.js index 17c6e55..000be9f 100644 --- a/tests/contentnormalizer.js +++ b/tests/contentnormalizer.js @@ -81,7 +81,7 @@ describe( 'ContentNormalizer', () => { activationTrigger: sinonTrigger } ); - normalizer.setInputData( data ); + normalizer.transform( data ); } ); it( 'should not be active', () => { From 7d32bc83c26c4ad813a7c93a1c340f122515ad0c Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Wed, 24 Jul 2019 11:48:09 +0200 Subject: [PATCH 20/48] Adapt unit test to new look of contentnormalizer. --- src/contentnormalizer.js | 2 +- tests/contentnormalizer.js | 190 +++++++++---------------------------- 2 files changed, 48 insertions(+), 144 deletions(-) diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 237e095..4876d64 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -33,7 +33,7 @@ export default class ContentNormalizer { _applyFilters( data ) { for ( const filter of this._filters ) { - filter( data ); + filter( { data } ); } } } diff --git a/tests/contentnormalizer.js b/tests/contentnormalizer.js index 000be9f..6a2d7ed 100644 --- a/tests/contentnormalizer.js +++ b/tests/contentnormalizer.js @@ -26,200 +26,104 @@ describe( 'ContentNormalizer', () => { } ); describe( 'constructor()', () => { - it( 'should be not active at start', () => { - expect( normalizer.isActive ).to.be.false; - } ); - - it( 'should not have assigned data', () => { - expect( normalizer.data ).to.be.null; - } ); - it( 'should have assigned activation trigger', () => { expect( normalizer.activationTrigger ).to.be.a( 'function' ); expect( normalizer.activationTrigger ).to.equal( sinonTrigger ); } ); - - it( 'should initialize sets for filters', () => { - expect( normalizer._filters ).to.be.a( 'set' ); - expect( normalizer._fullContentFilters ).to.be.a( 'set' ); - } ); } ); - describe( 'setInputData()', () => { + describe( 'transform()', () => { let data; beforeEach( () => { data = Object.assign( {}, templateData ); } ); - describe( 'trigger activated', () => { - beforeEach( () => { - normalizer.setInputData( data ); - } ); + describe( 'valid data', () => { + it( 'should mark data as transformed', () => { + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + + normalizer.transform( data ); - it( 'should set data', () => { - expect( normalizer.data ).to.equal = data; + expect( data.isTransformedWithPasteFromOffice ).to.be.true; } ); - it( 'should check if activates normalizer', () => { + it( 'should call for activation trigger to check input data', () => { + sinon.assert.notCalled( sinonTrigger ); + + normalizer.transform( data ); + sinon.assert.calledOnce( sinonTrigger ); sinon.assert.calledWith( sinonTrigger, 'test data' ); - - expect( normalizer.isActive ).to.be.true; } ); - it( 'should add flag to data processed by paste from office plugin', () => { - expect( data.isTransformedWithPasteFromOffice ).to.be.false; - } ); - } ); - - describe( 'trigger not activated', () => { - beforeEach( () => { - sinonTrigger = sinon.fake.returns( false ); - - normalizer = new ContentNormalizer( { - activationTrigger: sinonTrigger - } ); + it( 'should execute filters over data', () => { + const filter = sinon.fake(); + normalizer.addFilter( filter ); normalizer.transform( data ); + + sinon.assert.calledOnce( filter ); + sinon.assert.calledWith( filter, { data } ); } ); - it( 'should not be active', () => { - sinon.assert.calledOnce( sinonTrigger ); - sinon.assert.calledWith( sinonTrigger, 'test data' ); + it( 'should not process again already transformed data', () => { + const filter = sinon.fake(); - expect( normalizer.isActive ).to.be.false; - } ); + // Filters should not be executed + data.isTransformedWithPasteFromOffice = true; - it( 'should not keep reference to data when is not active', () => { - expect( normalizer.data ).to.be.null; - } ); + normalizer.addFilter( filter ); + normalizer.transform( data ); - it( 'should not add flag to not processed data', () => { - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + sinon.assert.notCalled( filter ); } ); } ); - describe( 'already processed data', () => { + describe( 'invalid data', () => { + let normalizer, sinonTrigger; + beforeEach( () => { - data.isTransformedWithPasteFromOffice = true; + sinonTrigger = sinon.fake.returns( false ); - normalizer.addFilter( { - fullContent: true, - // eslint-disable-next-line no-unused-vars - exec: d => { d = {}; } - } ); + normalizer = new ContentNormalizer( { activationTrigger: sinonTrigger } ); } ); - it( 'should not change data', () => { - normalizer.setInputData( data ); + it( 'should not change data content', () => { + normalizer.transform( data ); - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - expect( data ).to.deep.include( templateData ); + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + expect( data ).to.deep.equal( templateData ); } ); - } ); - } ); - describe( 'addFilter()', () => { - let filter; - describe( 'fullContentFilters', () => { - beforeEach( () => { - filter = { - fullContent: true, - exec: () => {} - }; + it( 'should not fire any filter', () => { + const filter = sinon.fake(); normalizer.addFilter( filter ); - } ); - - it( 'should add filter to fullContentFilters set', () => { - expect( normalizer._fullContentFilters.size ).to.equal( 1 ); - expect( normalizer._filters.size ).to.equal( 0 ); + normalizer.transform( data ); - const firstFilter = [ ...normalizer._fullContentFilters ][ 0 ]; - expect( firstFilter ).to.equal( filter ); + expect( normalizer._filters.size ).to.equal( 1 ); + sinon.assert.notCalled( filter ); } ); } ); } ); - describe( 'exec()', () => { - let data, filter; + describe( 'addFilter()', () => { + let filter; + beforeEach( () => { - data = Object.assign( {}, templateData ); filter = { - fullContent: true, - exec: data => { - data.content = 'Foo bar baz.'; - } + exec: () => {} }; normalizer.addFilter( filter ); } ); - it( 'should apply filter#exec to data', () => { - normalizer.setInputData( data ); - - expect( data.content ).to.be.undefined; - - normalizer.exec(); - - expect( data.content ).to.equal( 'Foo bar baz.' ); - } ); - - it( 'should mark data as processed with paste from office', () => { - normalizer.setInputData( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.false; - - normalizer.exec(); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); + it( 'should add filter to fullContentFilters set', () => { + expect( normalizer._filters.size ).to.equal( 1 ); - describe( 'already processed data', () => { - let execFake; - beforeEach( () => { - execFake = sinon.fake(); - normalizer = new ContentNormalizer( { activationTrigger: () => true } ); - - normalizer.addFilter( { - fullContent: true, - exec: execFake - } ); - } ); - - it( 'should not apply filter on already processed data', () => { - normalizer.setInputData( data ); - - sinon.assert.notCalled( execFake ); - expect( data.isTransformedWithPasteFromOffice ).to.be.false; - - normalizer.exec(); - sinon.assert.calledOnce( execFake ); - sinon.assert.calledWith( execFake, data ); - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - - normalizer.exec(); - sinon.assert.calledOnce( execFake ); - } ); - } ); - - describe( 'normalizer without filter', () => { - beforeEach( () => { - normalizer = new ContentNormalizer( { activationTrigger: () => true } ); - } ); - - it( 'should do nothing with data', () => { - normalizer.setInputData( data ); - - expect( data ).to.deep.include( templateData ); - expect( data.isTransformedWithPasteFromOffice ).to.be.false; - - normalizer.exec(); - - expect( data ).to.deep.include( templateData ); - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); + const firstFilter = [ ...normalizer._filters ][ 0 ]; + expect( firstFilter ).to.equal( filter ); } ); } ); } ); From 5c925dd8be50caedc04e770c4fb1030c1db04ec8 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Wed, 24 Jul 2019 12:53:52 +0200 Subject: [PATCH 21/48] Fix test for specific conten normalizers. --- src/normalizers/googledocs.js | 2 +- src/normalizers/msword.js | 2 +- tests/normalizers/googledocs.js | 40 +++++++++++++++++---------------- tests/normalizers/msword.js | 38 +++++++++++++++---------------- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/normalizers/googledocs.js b/src/normalizers/googledocs.js index fd66fbb..a16b4ea 100644 --- a/src/normalizers/googledocs.js +++ b/src/normalizers/googledocs.js @@ -15,7 +15,7 @@ export const googleDocsNormalizer = ( () => { activationTrigger: contentString => /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) } ); - normalizer.addFilter( data => { + normalizer.addFilter( ( { data } ) => { removeBoldTagWrapper( data.content ); } ); diff --git a/src/normalizers/msword.js b/src/normalizers/msword.js index 3498d17..b3a25b0 100644 --- a/src/normalizers/msword.js +++ b/src/normalizers/msword.js @@ -19,7 +19,7 @@ export const mswordNormalizer = ( () => { /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) } ); - normalizer.addFilter( data => { + normalizer.addFilter( ( { data } ) => { const html = data.dataTransfer.getData( 'text/html' ); data.content = _normalizeWordInput( html, data.dataTransfer ); diff --git a/tests/normalizers/googledocs.js b/tests/normalizers/googledocs.js index e1cd3b4..307bd30 100644 --- a/tests/normalizers/googledocs.js +++ b/tests/normalizers/googledocs.js @@ -13,10 +13,6 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/html describe( 'PasteFromOffice/normalizers/googledocs', () => { const htmlDataProcessor = new HtmlDataProcessor(); - afterEach( () => { - normalizer.setInputData( {} ); - } ); - it( 'should be instance of content normalizers', () => { expect( normalizer ).to.be.instanceOf( ContentNormalizer ); } ); @@ -30,7 +26,7 @@ describe( 'PasteFromOffice/normalizers/googledocs', () => { content: htmlDataProcessor.toView( gDocs ) }; - normalizer.setInputData( data ).exec(); + normalizer.transform( data ); expect( data.isTransformedWithPasteFromOffice ).to.be.true; } ); @@ -40,7 +36,7 @@ describe( 'PasteFromOffice/normalizers/googledocs', () => { dataTransfer: createDataTransfer( { 'text/html': 'foo bar' } ) }; - normalizer.setInputData( data ).exec(); + normalizer.transform( data ); expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; } ); @@ -54,7 +50,7 @@ describe( 'PasteFromOffice/normalizers/googledocs', () => { content: htmlDataProcessor.toView( gDocs ) }; - normalizer.setInputData( data ).exec(); + normalizer.transform( data ); expect( data.isTransformedWithPasteFromOffice ).to.be.true; expect( data.content ).to.be.instanceOf( DocumentFragment ); @@ -70,15 +66,18 @@ describe( 'PasteFromOffice/normalizers/googledocs', () => { // eslint-disable-next-line max-len 'text/html': '' } - ].forEach( ( data, index ) => { + ].forEach( ( html, index ) => { it( `should be active for markup #${ index }`, () => { - expect( normalizer.isActive ).to.be.false; + const data = { + dataTransfer: createDataTransfer( html ), + content: htmlDataProcessor.toView( html[ 'text/html' ] ) + }; - normalizer.setInputData( { - dataTransfer: createDataTransfer( data ) - } ); + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - expect( normalizer.isActive ).to.be.true; + normalizer.transform( data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; } ); } ); } ); @@ -91,15 +90,18 @@ describe( 'PasteFromOffice/normalizers/googledocs', () => { { 'text/html': '' } - ].forEach( ( data, index ) => { + ].forEach( ( html, index ) => { it( `should be not active for wrong markup #${ index }`, () => { - expect( normalizer.isActive ).to.be.false; + const data = { + dataTransfer: createDataTransfer( html ), + content: htmlDataProcessor.toView( html[ 'text/html' ] ) + }; + + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - normalizer.setInputData( { - dataTransfer: createDataTransfer( data ) - } ); + normalizer.transform( data ); - expect( normalizer.isActive ).to.be.false; + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; } ); } ); } ); diff --git a/tests/normalizers/msword.js b/tests/normalizers/msword.js index faf3652..dfd3f51 100644 --- a/tests/normalizers/msword.js +++ b/tests/normalizers/msword.js @@ -10,10 +10,6 @@ import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragme // Functionality of the msword normalizer is tested with autogenerated normalization tests. describe( 'PasteFromOffice/normalizers/msword', () => { - afterEach( () => { - normalizer.setInputData( {} ); - } ); - it( 'should be instance of content normalizers', () => { expect( normalizer ).to.be.instanceOf( ContentNormalizer ); } ); @@ -25,7 +21,7 @@ describe( 'PasteFromOffice/normalizers/msword', () => { } ) }; - normalizer.setInputData( data ).exec(); + normalizer.transform( data ); expect( data.isTransformedWithPasteFromOffice ).to.be.true; } ); @@ -35,7 +31,7 @@ describe( 'PasteFromOffice/normalizers/msword', () => { dataTransfer: createDataTransfer( { 'text/html': 'foo bar' } ) }; - normalizer.setInputData( data ).exec(); + normalizer.transform( data ); expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; } ); @@ -47,7 +43,7 @@ describe( 'PasteFromOffice/normalizers/msword', () => { } ) }; - normalizer.setInputData( data ).exec(); + normalizer.transform( data ); expect( data.content ).to.be.instanceOf( DocumentFragment ); } ); @@ -61,15 +57,17 @@ describe( 'PasteFromOffice/normalizers/msword', () => { { 'text/html': '' } - ].forEach( ( data, index ) => { + ].forEach( ( html, index ) => { it( `should be active for markup #${ index }`, () => { - expect( normalizer.isActive ).to.be.false; + const data = { + dataTransfer: createDataTransfer( html ) + }; - normalizer.setInputData( { - dataTransfer: createDataTransfer( data ) - } ); + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - expect( normalizer.isActive ).to.be.true; + normalizer.transform( data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; } ); } ); } ); @@ -82,15 +80,17 @@ describe( 'PasteFromOffice/normalizers/msword', () => { { 'text/html': '

' } - ].forEach( ( data, index ) => { + ].forEach( ( html, index ) => { it( `should be not active for wrong markup #${ index }`, () => { - expect( normalizer.isActive ).to.be.false; + const data = { + dataTransfer: createDataTransfer( html ) + }; + + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - normalizer.setInputData( { - dataTransfer: createDataTransfer( data ) - } ); + normalizer.transform( data ); - expect( normalizer.isActive ).to.be.false; + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; } ); } ); } ); From 0017a4bb4f974385d282ca7cfed362f50880d3ba Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Wed, 24 Jul 2019 13:01:59 +0200 Subject: [PATCH 22/48] Remove unnecessary propeties form PFO class. --- src/pastefromoffice.js | 18 +++++------------- tests/pastefromoffice.js | 12 ------------ 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 59761b3..84c6cd1 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -24,18 +24,6 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; * @extends module:core/plugin~Plugin */ export default class PasteFromOffice extends Plugin { - /** - * @inheritDoc - */ - constructor( editor ) { - super( editor ); - - this._normalizers = new Set(); - - this._normalizers.add( mswordNormalizer ); - this._normalizers.add( googleDocsNormalizer ); - } - /** * @inheritDoc */ @@ -55,12 +43,16 @@ export default class PasteFromOffice extends Plugin { */ init() { const editor = this.editor; + const normalizers = new Set(); + + normalizers.add( mswordNormalizer ); + normalizers.add( googleDocsNormalizer ); this.listenTo( editor.plugins.get( 'Clipboard' ), 'inputTransformation', ( evt, data ) => { - for ( const normalizer of this._normalizers ) { + for ( const normalizer of normalizers ) { normalizer.transform( data ); } }, diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 742f1a9..ac664f0 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -6,7 +6,6 @@ import PasteFromOffice from '../src/pastefromoffice'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; -import ContentNormalizer from '../src/contentnormalizer'; describe( 'PasteFromOffice', () => { let editor, pasteFromOffice; @@ -31,15 +30,4 @@ describe( 'PasteFromOffice', () => { it( 'should load Clipboard plugin', () => { expect( editor.plugins.get( Clipboard ) ).to.be.instanceOf( Clipboard ); } ); - - describe( 'constructor()', () => { - it( 'should initialize 2 normalizers', () => { - expect( pasteFromOffice._normalizers ).to.be.a( 'set' ); - expect( pasteFromOffice._normalizers.size ).to.equal( 2 ); - - pasteFromOffice._normalizers.forEach( value => { - expect( value ).to.be.instanceOf( ContentNormalizer ); - } ); - } ); - } ); } ); From 318b67c82229121b71c9f2a097dbe54b727742ca Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Wed, 24 Jul 2019 16:58:20 +0200 Subject: [PATCH 23/48] Docs and small improvment for content noralizer filters. --- src/contentnormalizer.js | 127 ++++++++++++++++++++++++++++++++-- src/filters/common.js | 9 +-- src/normalizers/googledocs.js | 16 +++-- src/normalizers/msword.js | 15 ++-- src/pastefromoffice.js | 10 ++- tests/contentnormalizer.js | 25 ++++--- tests/filters/common.js | 13 +++- 7 files changed, 176 insertions(+), 39 deletions(-) diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 4876d64..1f46140 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -7,33 +7,148 @@ * @module paste-from-office/contentnormalizer */ +import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; + +/** + * Content Normalizer class provides a mechanism to transform input data send through + * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. It fixes an input content, + * which has a source in applications like: MS Word, Google Docs, etc. These applications generate content which frequently + * is an invalid HTML. Content normalizers transform it, what later might be properly upcast to {@link module:engine/model/model~Model}. + * + * Content Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance is + * initialized with an activation trigger. Activation trigger is a function which gets content of `text/html` dataTransfer (String) and + * returns `true` or `false`. Based on this result normalizer applies filters to given data. + * + * Filters are function, which are run sequentially, as they were added. Each filter gets data transformed by the previous one. + * + * Example definition: + * + * const normalizer = new ContentNormalizer( contentHtml => + * contentHtml.includes( 'docs-internal-guid' ) + * ); + * + * normalizer.addFilter( ( { data } ) => { + * removeBoldTagWrapper( data.content ); + * } ) + * + * normalizer.addFilter( ( { data } ) => { + * // ... + * // another modification of data's content + * } ); + * + * Normalizers are stored inside Paste from Office plugin and are run on + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Below example is simplified and show + * how to call normalizer directly on clipboard event. + * + * editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => { + * normalizer.transform( data ); + * } ); + * + * @class + */ export default class ContentNormalizer { - constructor( { activationTrigger } ) { - this.activationTrigger = activationTrigger; + /** + * Initialize Content Normalizer. + * + * @param {Function} activationTrigger The function which checks for what content should be applied this normalizer. + * It takes an HTML string from the `text/html` dataTarnsfer as an argument and have to return a boolean value + */ + constructor( activationTrigger ) { + /** + * Keeps a reference to the activation trigger function. The function is used to check if current Content Normalizer instance + * should be applied for given input data. Check is made during the {@link #transform}. + * + * @private + * @type {Function} + */ + this._activationTrigger = activationTrigger; + /** + * Keeps a reference to registered filters with {@link #addFilter} method. + * + * @private + * @type {Set} + */ this._filters = new Set(); } + /** + * Method checks if passed data should have applied {@link #_filters} registerd in this Content Normalizer. + * If yes, then data are transformed and marked with a flag `isTransformedWithPasteFromOffice = true`. + * In other case data are not modified. + * + * Please notice that presence of `isTransformedWithPasteFromOffice` flag in input data prevent transformation. + * This forbid of running the same normalizer twice or running multiple normalizers over the same data. + * + * @param data input data object it should preserve structure defined in + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. + */ transform( data ) { const html = data.dataTransfer && data.dataTransfer.getData( 'text/html' ); const dataReadFirstTime = data.isTransformedWithPasteFromOffice === undefined; const hasHtmlData = !!html; - if ( hasHtmlData && dataReadFirstTime && this.activationTrigger( html ) ) { + if ( hasHtmlData && dataReadFirstTime && this._activationTrigger( html ) ) { this._applyFilters( data ); data.isTransformedWithPasteFromOffice = true; } - - return this; } + /** + * Adds filter function to Content Normalizer. + * Function is called with configuration object where `data` key keeps reference to input data obtained from + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event} + * + * See also: {@link module:paste-from-office/contentnormalizer~FilterFunction} + * + * @param {module:paste-from-office/contentnormalizer~FilterFunction} filterFn + */ addFilter( filterFn ) { this._filters.add( filterFn ); } + /** + * Applies filters stored in {@link #_filters} to currently processed data. + * + * @private + * @param {Object} data input data object it should preserve structure defined in + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. + */ _applyFilters( data ) { + const writer = new UpcastWriter(); + const documentFragment = data.content; + for ( const filter of this._filters ) { - filter( { data } ); + filter( { data, documentFragment, writer } ); } } } + +/** + * Filter function which is used to transform data of + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. + * + * Filters are used by {@link module:paste-from-office/contentnormalizer~ContentNormalizer}. + * + * Example: + * + * function removeBoldTagWrapper( { documentFragment, writer } ) { + * for ( const childWithWrapper of documentFragment.getChildren() ) { + * if ( childWithWrapper.is( 'b' ) && childWithWrapper.getStyle( 'font-weight' ) === 'normal' ) { + * const childIndex = documentFragment.getChildIndex( childWithWrapper ); + * const removedElement = writer.remove( childWithWrapper )[ 0 ]; + * + * writer.insertChild( childIndex, removedElement.getChildren(), documentFragment ); + * } + * } + * } + * + * @callback module:paste-from-office/contentnormalizer~FilterFunction + * @param {Object} config + * @param {Object} config.data input data object it should preserve structure defined in + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. + * @param {module:engine/view/upcastwriter~UpcastWriter} config.writer upcast writer which can be used to manipulate + * with document fragment. + * @param {module:engine/view/documentfragment~DocumentFragment} config.documentFragment the `data.content` obtained from + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event} + */ diff --git a/src/filters/common.js b/src/filters/common.js index 9728842..ccbde01 100644 --- a/src/filters/common.js +++ b/src/filters/common.js @@ -11,17 +11,14 @@ * The filter removes `` tag wrapper added by Google Docs for copied content. * * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment - * @returns {module:engine/view/documentfragment~DocumentFragment} */ -export function removeBoldTagWrapper( documentFragment ) { +export function removeBoldTagWrapper( { documentFragment, writer } ) { for ( const childWithWrapper of documentFragment.getChildren() ) { if ( childWithWrapper.is( 'b' ) && childWithWrapper.getStyle( 'font-weight' ) === 'normal' ) { const childIndex = documentFragment.getChildIndex( childWithWrapper ); + const removedElement = writer.remove( childWithWrapper )[ 0 ]; - documentFragment._removeChildren( childIndex ); - documentFragment._insertChild( childIndex, childWithWrapper.getChildren() ); + writer.insertChild( childIndex, removedElement.getChildren(), documentFragment ); } } - - return documentFragment; } diff --git a/src/normalizers/googledocs.js b/src/normalizers/googledocs.js index a16b4ea..0c53124 100644 --- a/src/normalizers/googledocs.js +++ b/src/normalizers/googledocs.js @@ -10,14 +10,18 @@ import ContentNormalizer from '../contentnormalizer'; import { removeBoldTagWrapper } from '../filters/common'; +/** + * {@link module:paste-from-office/contentnormalizer~ContentNormalizer} instance dedicated to transforming data obtained from Google Docs. + * It stores filters which fix quirks detected in Google Docs content. + * + * @type {module:paste-from-office/contentnormalizer~ContentNormalizer} + */ export const googleDocsNormalizer = ( () => { - const normalizer = new ContentNormalizer( { - activationTrigger: contentString => /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) - } ); + const normalizer = new ContentNormalizer( contentString => + /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) + ); - normalizer.addFilter( ( { data } ) => { - removeBoldTagWrapper( data.content ); - } ); + normalizer.addFilter( removeBoldTagWrapper ); return normalizer; } )(); diff --git a/src/normalizers/msword.js b/src/normalizers/msword.js index b3a25b0..766b84d 100644 --- a/src/normalizers/msword.js +++ b/src/normalizers/msword.js @@ -12,12 +12,17 @@ import { parseHtml } from '../filters/parse'; import { transformListItemLikeElementsIntoLists } from '../filters/list'; import { replaceImagesSourceWithBase64 } from '../filters/image'; +/** + * {@link module:paste-from-office/contentnormalizer~ContentNormalizer} instance dedicated to transforming data obtained from MS Word. + * It stores filters which fix quirks detected in MS Word content. + * + * @type {module:paste-from-office/contentnormalizer~ContentNormalizer} + */ export const mswordNormalizer = ( () => { - const normalizer = new ContentNormalizer( { - activationTrigger: contentString => - //i.test( contentString ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) - } ); + const normalizer = new ContentNormalizer( contentString => + //i.test( contentString ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) + ); normalizer.addFilter( ( { data } ) => { const html = data.dataTransfer.getData( 'text/html' ); diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 84c6cd1..8795a35 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -16,9 +16,14 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; /** * The Paste from Office plugin. * - * This plugin handles content pasted from Office apps (for now only Word) and transforms it (if necessary) + * This plugin handles content pasted from Office apps and transforms it (if necessary) * to a valid structure which can then be understood by the editor features. * + * Transformation is made by a set of predefined {@link module:paste-from-office/contentnormalizer~ContentNormalizer}. + * Currently, there are included followed normalizers: + * * {@link module:paste-from-office/normalizer.mswordNormalizer MS Word normalizer} + * * {@link module:paste-from-office/normalizer.googleDocsNormalizer Google Docs normalizer} + * * For more information about this feature check the {@glink api/paste-from-office package page}. * * @extends module:core/plugin~Plugin @@ -48,8 +53,7 @@ export default class PasteFromOffice extends Plugin { normalizers.add( mswordNormalizer ); normalizers.add( googleDocsNormalizer ); - this.listenTo( - editor.plugins.get( 'Clipboard' ), + editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => { for ( const normalizer of normalizers ) { diff --git a/tests/contentnormalizer.js b/tests/contentnormalizer.js index 6a2d7ed..32b7aa9 100644 --- a/tests/contentnormalizer.js +++ b/tests/contentnormalizer.js @@ -6,6 +6,7 @@ import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; import ContentNormalizer from '../src/contentnormalizer'; import { createDataTransfer } from './_utils/utils'; +import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; describe( 'ContentNormalizer', () => { let normalizer, sinonTrigger; @@ -20,15 +21,13 @@ describe( 'ContentNormalizer', () => { beforeEach( () => { sinonTrigger = sinon.fake.returns( true ); - normalizer = new ContentNormalizer( { - activationTrigger: sinonTrigger - } ); + normalizer = new ContentNormalizer( sinonTrigger ); } ); describe( 'constructor()', () => { it( 'should have assigned activation trigger', () => { - expect( normalizer.activationTrigger ).to.be.a( 'function' ); - expect( normalizer.activationTrigger ).to.equal( sinonTrigger ); + expect( normalizer._activationTrigger ).to.be.a( 'function' ); + expect( normalizer._activationTrigger ).to.equal( sinonTrigger ); } ); } ); @@ -59,12 +58,20 @@ describe( 'ContentNormalizer', () => { it( 'should execute filters over data', () => { const filter = sinon.fake(); + const writer = new UpcastWriter(); + const documentFragment = writer.createDocumentFragment(); + + data.content = documentFragment; normalizer.addFilter( filter ); normalizer.transform( data ); sinon.assert.calledOnce( filter ); - sinon.assert.calledWith( filter, { data } ); + sinon.assert.calledWithMatch( filter, { + documentFragment, + data, + writer: sinon.match.instanceOf( UpcastWriter ) + } ); } ); it( 'should not process again already transformed data', () => { @@ -86,7 +93,7 @@ describe( 'ContentNormalizer', () => { beforeEach( () => { sinonTrigger = sinon.fake.returns( false ); - normalizer = new ContentNormalizer( { activationTrigger: sinonTrigger } ); + normalizer = new ContentNormalizer( sinonTrigger ); } ); it( 'should not change data content', () => { @@ -112,9 +119,7 @@ describe( 'ContentNormalizer', () => { let filter; beforeEach( () => { - filter = { - exec: () => {} - }; + filter = () => {}; normalizer.addFilter( filter ); } ); diff --git a/tests/filters/common.js b/tests/filters/common.js index e9abb59..c98dacf 100644 --- a/tests/filters/common.js +++ b/tests/filters/common.js @@ -5,18 +5,25 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; import { removeBoldTagWrapper } from '../../src/filters/common'; +import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; describe( 'PasteFromOffice/filters', () => { const htmlDataProcessor = new HtmlDataProcessor(); describe( 'common', () => { describe( 'removeBoldTagWrapper', () => { + let writer; + + before( () => { + writer = new UpcastWriter(); + } ); + it( 'should remove bold wrapper added by google docs', () => { const inputData = '' + '

Hello world

' + '
'; const documentFragment = htmlDataProcessor.toView( inputData ); - removeBoldTagWrapper( documentFragment ); + removeBoldTagWrapper( { documentFragment, writer } ); expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); } ); @@ -25,7 +32,7 @@ describe( 'PasteFromOffice/filters', () => { const inputData = '

Hello world

'; const documentFragment = htmlDataProcessor.toView( inputData ); - removeBoldTagWrapper( documentFragment ); + removeBoldTagWrapper( { documentFragment, writer } ); expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); @@ -35,7 +42,7 @@ describe( 'PasteFromOffice/filters', () => { const inputData = 'Hello world'; const documentFragment = htmlDataProcessor.toView( inputData ); - removeBoldTagWrapper( documentFragment ); + removeBoldTagWrapper( { documentFragment, writer } ); expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( 'Hello world' ); From 0948fc14216b9808c10f4fcc185c0ab9b37aeaa4 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Thu, 25 Jul 2019 10:17:44 +0200 Subject: [PATCH 24/48] Fix docs typos. --- src/contentnormalizer.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js index 1f46140..cf5eda1 100644 --- a/src/contentnormalizer.js +++ b/src/contentnormalizer.js @@ -19,7 +19,8 @@ import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; * initialized with an activation trigger. Activation trigger is a function which gets content of `text/html` dataTransfer (String) and * returns `true` or `false`. Based on this result normalizer applies filters to given data. * - * Filters are function, which are run sequentially, as they were added. Each filter gets data transformed by the previous one. + * {@link module:paste-from-office/contentnormalizer~FilterFunction Filters} are function, which are run sequentially, as they were added. + * Each filter gets data transformed by the previous one. * * Example definition: * @@ -27,18 +28,14 @@ import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; * contentHtml.includes( 'docs-internal-guid' ) * ); * - * normalizer.addFilter( ( { data } ) => { - * removeBoldTagWrapper( data.content ); - * } ) - * - * normalizer.addFilter( ( { data } ) => { - * // ... - * // another modification of data's content + * normalizer.addFilter( ( { data, documentFragment, writer } ) => { + * // filter's content, which transforms data in clipboard * } ); * * Normalizers are stored inside Paste from Office plugin and are run on * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Below example is simplified and show - * how to call normalizer directly on clipboard event. + * how to call normalizer directly on clipboard event, what has happen inside + * {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. * * editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => { * normalizer.transform( data ); @@ -51,7 +48,7 @@ export default class ContentNormalizer { * Initialize Content Normalizer. * * @param {Function} activationTrigger The function which checks for what content should be applied this normalizer. - * It takes an HTML string from the `text/html` dataTarnsfer as an argument and have to return a boolean value + * It takes an HTML string from the `text/html` dataTransfer as an argument and have to return a boolean value */ constructor( activationTrigger ) { /** @@ -74,7 +71,7 @@ export default class ContentNormalizer { /** * Method checks if passed data should have applied {@link #_filters} registerd in this Content Normalizer. - * If yes, then data are transformed and marked with a flag `isTransformedWithPasteFromOffice = true`. + * If yes, then data are transformed and marked with a flag `isTransformedWithPasteFromOffice=true`. * In other case data are not modified. * * Please notice that presence of `isTransformedWithPasteFromOffice` flag in input data prevent transformation. @@ -130,7 +127,7 @@ export default class ContentNormalizer { * * Filters are used by {@link module:paste-from-office/contentnormalizer~ContentNormalizer}. * - * Example: + * Examples: * * function removeBoldTagWrapper( { documentFragment, writer } ) { * for ( const childWithWrapper of documentFragment.getChildren() ) { @@ -143,6 +140,12 @@ export default class ContentNormalizer { * } * } * + * function transformWordContent( { data } ) { + * const html = data.dataTransfer.getData( 'text/html' ); + * + * data.content = _normalizeWordInput( html, data.dataTransfer ); + * } + * * @callback module:paste-from-office/contentnormalizer~FilterFunction * @param {Object} config * @param {Object} config.data input data object it should preserve structure defined in From a63bcc116351a89ce67a56a3e3992b0b170c9063 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Thu, 25 Jul 2019 12:38:53 +0200 Subject: [PATCH 25/48] Apply suggestions from code review Co-Authored-By: Maciej --- src/filters/common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filters/common.js b/src/filters/common.js index ccbde01..1e77698 100644 --- a/src/filters/common.js +++ b/src/filters/common.js @@ -8,7 +8,7 @@ */ /** - * The filter removes `` tag wrapper added by Google Docs for copied content. + * Removes `` tag wrapper added by Google Docs to a copied content. * * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment */ From 751f615717b3c45cfd61ef70294f9e3f68870826 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 26 Jul 2019 09:26:26 +0200 Subject: [PATCH 26/48] Make interaace from the normalizer. Apply interface to msword and google normalizers. Fix tests. --- src/contentnormalizer.js | 157 -------------------------------- src/normalizer.jsdoc | 39 ++++++++ src/normalizers/googledocs.js | 32 ++++--- src/normalizers/msword.js | 36 ++++---- src/pastefromoffice.js | 29 ++++-- tests/contentnormalizer.js | 134 --------------------------- tests/normalizers/googledocs.js | 106 +++------------------ tests/normalizers/msword.js | 95 ++++--------------- 8 files changed, 129 insertions(+), 499 deletions(-) delete mode 100644 src/contentnormalizer.js create mode 100644 src/normalizer.jsdoc delete mode 100644 tests/contentnormalizer.js diff --git a/src/contentnormalizer.js b/src/contentnormalizer.js deleted file mode 100644 index cf5eda1..0000000 --- a/src/contentnormalizer.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module paste-from-office/contentnormalizer - */ - -import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; - -/** - * Content Normalizer class provides a mechanism to transform input data send through - * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. It fixes an input content, - * which has a source in applications like: MS Word, Google Docs, etc. These applications generate content which frequently - * is an invalid HTML. Content normalizers transform it, what later might be properly upcast to {@link module:engine/model/model~Model}. - * - * Content Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance is - * initialized with an activation trigger. Activation trigger is a function which gets content of `text/html` dataTransfer (String) and - * returns `true` or `false`. Based on this result normalizer applies filters to given data. - * - * {@link module:paste-from-office/contentnormalizer~FilterFunction Filters} are function, which are run sequentially, as they were added. - * Each filter gets data transformed by the previous one. - * - * Example definition: - * - * const normalizer = new ContentNormalizer( contentHtml => - * contentHtml.includes( 'docs-internal-guid' ) - * ); - * - * normalizer.addFilter( ( { data, documentFragment, writer } ) => { - * // filter's content, which transforms data in clipboard - * } ); - * - * Normalizers are stored inside Paste from Office plugin and are run on - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Below example is simplified and show - * how to call normalizer directly on clipboard event, what has happen inside - * {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. - * - * editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => { - * normalizer.transform( data ); - * } ); - * - * @class - */ -export default class ContentNormalizer { - /** - * Initialize Content Normalizer. - * - * @param {Function} activationTrigger The function which checks for what content should be applied this normalizer. - * It takes an HTML string from the `text/html` dataTransfer as an argument and have to return a boolean value - */ - constructor( activationTrigger ) { - /** - * Keeps a reference to the activation trigger function. The function is used to check if current Content Normalizer instance - * should be applied for given input data. Check is made during the {@link #transform}. - * - * @private - * @type {Function} - */ - this._activationTrigger = activationTrigger; - - /** - * Keeps a reference to registered filters with {@link #addFilter} method. - * - * @private - * @type {Set} - */ - this._filters = new Set(); - } - - /** - * Method checks if passed data should have applied {@link #_filters} registerd in this Content Normalizer. - * If yes, then data are transformed and marked with a flag `isTransformedWithPasteFromOffice=true`. - * In other case data are not modified. - * - * Please notice that presence of `isTransformedWithPasteFromOffice` flag in input data prevent transformation. - * This forbid of running the same normalizer twice or running multiple normalizers over the same data. - * - * @param data input data object it should preserve structure defined in - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. - */ - transform( data ) { - const html = data.dataTransfer && data.dataTransfer.getData( 'text/html' ); - const dataReadFirstTime = data.isTransformedWithPasteFromOffice === undefined; - const hasHtmlData = !!html; - - if ( hasHtmlData && dataReadFirstTime && this._activationTrigger( html ) ) { - this._applyFilters( data ); - data.isTransformedWithPasteFromOffice = true; - } - } - - /** - * Adds filter function to Content Normalizer. - * Function is called with configuration object where `data` key keeps reference to input data obtained from - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event} - * - * See also: {@link module:paste-from-office/contentnormalizer~FilterFunction} - * - * @param {module:paste-from-office/contentnormalizer~FilterFunction} filterFn - */ - addFilter( filterFn ) { - this._filters.add( filterFn ); - } - - /** - * Applies filters stored in {@link #_filters} to currently processed data. - * - * @private - * @param {Object} data input data object it should preserve structure defined in - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. - */ - _applyFilters( data ) { - const writer = new UpcastWriter(); - const documentFragment = data.content; - - for ( const filter of this._filters ) { - filter( { data, documentFragment, writer } ); - } - } -} - -/** - * Filter function which is used to transform data of - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. - * - * Filters are used by {@link module:paste-from-office/contentnormalizer~ContentNormalizer}. - * - * Examples: - * - * function removeBoldTagWrapper( { documentFragment, writer } ) { - * for ( const childWithWrapper of documentFragment.getChildren() ) { - * if ( childWithWrapper.is( 'b' ) && childWithWrapper.getStyle( 'font-weight' ) === 'normal' ) { - * const childIndex = documentFragment.getChildIndex( childWithWrapper ); - * const removedElement = writer.remove( childWithWrapper )[ 0 ]; - * - * writer.insertChild( childIndex, removedElement.getChildren(), documentFragment ); - * } - * } - * } - * - * function transformWordContent( { data } ) { - * const html = data.dataTransfer.getData( 'text/html' ); - * - * data.content = _normalizeWordInput( html, data.dataTransfer ); - * } - * - * @callback module:paste-from-office/contentnormalizer~FilterFunction - * @param {Object} config - * @param {Object} config.data input data object it should preserve structure defined in - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event}. - * @param {module:engine/view/upcastwriter~UpcastWriter} config.writer upcast writer which can be used to manipulate - * with document fragment. - * @param {module:engine/view/documentfragment~DocumentFragment} config.documentFragment the `data.content` obtained from - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation Clipboard#inputTransformation event} - */ diff --git a/src/normalizer.jsdoc b/src/normalizer.jsdoc new file mode 100644 index 0000000..2f132d6 --- /dev/null +++ b/src/normalizer.jsdoc @@ -0,0 +1,39 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/normalizer + */ + +/** + * Normalizer interface is description of a mechanism which transforms input data send through + * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Input content is transformed + * for data came from office-like applications, for example, : MS Word, Google Docs, etc. These applications generate content, + * which frequently has an invalid HTML syntax. Normalizers detects environment specific quirks and transform it to proper HTML syntax, + * what later might be properly upcast to the {@link module:engine/model/model~Model}. + * + * Content Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance is + * initialized with an activation trigger. Activation trigger is a function which gets content of `text/html` dataTransfer (String) and + * returns `true` or `false`. Based on this result normalizer applies filters to given data. + * + * @interface Normalizer + */ + +/** + * Method determines if current normalizer should be run for given content. It takes an HTML string from `data.dataTransfer` as an argument + * and it should return a boolean value. + * + * @method #isActive + * @param {String} htmlString full content of `dataTransfer.getData( 'text/html' )` + * @returns {Boolean} + */ + +/** + * Method applies normalization to given data. + * + * @method #exec + * @param {Object} data object obtained from + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event} + */ diff --git a/src/normalizers/googledocs.js b/src/normalizers/googledocs.js index 0c53124..66c25aa 100644 --- a/src/normalizers/googledocs.js +++ b/src/normalizers/googledocs.js @@ -7,21 +7,31 @@ * @module paste-from-office/normalizer */ -import ContentNormalizer from '../contentnormalizer'; import { removeBoldTagWrapper } from '../filters/common'; +import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; /** - * {@link module:paste-from-office/contentnormalizer~ContentNormalizer} instance dedicated to transforming data obtained from Google Docs. - * It stores filters which fix quirks detected in Google Docs content. + * Normalizer fixing HTML syntax obtained from Google Docs. * - * @type {module:paste-from-office/contentnormalizer~ContentNormalizer} + * @implements module:paste-from-office/normalizer~Normalizer */ -export const googleDocsNormalizer = ( () => { - const normalizer = new ContentNormalizer( contentString => - /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( contentString ) - ); +export default class GoogleDocsNormalizer { + /** + * @inheritDoc + */ + isActive( htmlString ) { + return /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( htmlString ); + } - normalizer.addFilter( removeBoldTagWrapper ); + /** + * @inheritDoc + */ + exec( data ) { + const writer = new UpcastWriter(); - return normalizer; -} )(); + removeBoldTagWrapper( { + writer, + documentFragment: data.content + } ); + } +} diff --git a/src/normalizers/msword.js b/src/normalizers/msword.js index 766b84d..49faf77 100644 --- a/src/normalizers/msword.js +++ b/src/normalizers/msword.js @@ -7,31 +7,33 @@ * @module paste-from-office/normalizer */ -import ContentNormalizer from '../contentnormalizer'; import { parseHtml } from '../filters/parse'; import { transformListItemLikeElementsIntoLists } from '../filters/list'; import { replaceImagesSourceWithBase64 } from '../filters/image'; /** - * {@link module:paste-from-office/contentnormalizer~ContentNormalizer} instance dedicated to transforming data obtained from MS Word. - * It stores filters which fix quirks detected in MS Word content. + * Normalizer fixing HTML syntax obtained from Microsoft Word documents. * - * @type {module:paste-from-office/contentnormalizer~ContentNormalizer} + * @implements module:paste-from-office/normalizer~Normalizer */ -export const mswordNormalizer = ( () => { - const normalizer = new ContentNormalizer( contentString => - //i.test( contentString ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( contentString ) - ); - - normalizer.addFilter( ( { data } ) => { +export default class MSWordNormalizer { + /** + * @inheritDoc + */ + isActive( htmlString ) { + return //i.test( htmlString ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( htmlString ); + } + + /** + * @inheritDoc + */ + exec( data ) { const html = data.dataTransfer.getData( 'text/html' ); - data.content = _normalizeWordInput( html, data.dataTransfer ); - } ); - - return normalizer; -} )(); + data.content = normalizeWordInput( html, data.dataTransfer ); + } +} // // Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. @@ -41,7 +43,7 @@ export const mswordNormalizer = ( () => { // @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. // @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. // -function _normalizeWordInput( input, dataTransfer ) { +function normalizeWordInput( input, dataTransfer ) { const { body, stylesString } = parseHtml( input ); transformListItemLikeElementsIntoLists( body, stylesString ); diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 8795a35..b215e4a 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,8 +9,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import { googleDocsNormalizer } from './normalizers/googledocs'; -import { mswordNormalizer } from './normalizers/msword'; +import GoogleDocsNormalizer from './normalizers/googledocs'; +import MSWordNormalizer from './normalizers/msword'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; /** @@ -19,10 +19,10 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; * This plugin handles content pasted from Office apps and transforms it (if necessary) * to a valid structure which can then be understood by the editor features. * - * Transformation is made by a set of predefined {@link module:paste-from-office/contentnormalizer~ContentNormalizer}. + * Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer}. * Currently, there are included followed normalizers: - * * {@link module:paste-from-office/normalizer.mswordNormalizer MS Word normalizer} - * * {@link module:paste-from-office/normalizer.googleDocsNormalizer Google Docs normalizer} + * * {@link module:paste-from-office/normalizer~MSWordNormalizer MS Word normalizer} + * * {@link module:paste-from-office/normalizer~GoogleDocsNormalizer Google Docs normalizer} * * For more information about this feature check the {@glink api/paste-from-office package page}. * @@ -48,16 +48,25 @@ export default class PasteFromOffice extends Plugin { */ init() { const editor = this.editor; - const normalizers = new Set(); + const normalizers = []; - normalizers.add( mswordNormalizer ); - normalizers.add( googleDocsNormalizer ); + normalizers.push( new MSWordNormalizer() ); + normalizers.push( new GoogleDocsNormalizer() ); editor.plugins.get( 'Clipboard' ).on( 'inputTransformation', ( evt, data ) => { - for ( const normalizer of normalizers ) { - normalizer.transform( data ); + if ( data.isTransformedWithPasteFromOffice ) { + return; + } + + const htmlString = data.dataTransfer.getData( 'text/html' ); + const activeNormalizer = normalizers.find( normalizer => normalizer.isActive( htmlString ) ); + + if ( activeNormalizer ) { + activeNormalizer.exec( data ); + + data.isTransformedWithPasteFromOffice = true; } }, { priority: 'high' } diff --git a/tests/contentnormalizer.js b/tests/contentnormalizer.js deleted file mode 100644 index 32b7aa9..0000000 --- a/tests/contentnormalizer.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; -import ContentNormalizer from '../src/contentnormalizer'; -import { createDataTransfer } from './_utils/utils'; -import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; - -describe( 'ContentNormalizer', () => { - let normalizer, sinonTrigger; - const templateData = { - dataTransfer: createDataTransfer( { - 'text/html': 'test data' - } ) - }; - - testUtils.createSinonSandbox(); - - beforeEach( () => { - sinonTrigger = sinon.fake.returns( true ); - - normalizer = new ContentNormalizer( sinonTrigger ); - } ); - - describe( 'constructor()', () => { - it( 'should have assigned activation trigger', () => { - expect( normalizer._activationTrigger ).to.be.a( 'function' ); - expect( normalizer._activationTrigger ).to.equal( sinonTrigger ); - } ); - } ); - - describe( 'transform()', () => { - let data; - - beforeEach( () => { - data = Object.assign( {}, templateData ); - } ); - - describe( 'valid data', () => { - it( 'should mark data as transformed', () => { - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); - - it( 'should call for activation trigger to check input data', () => { - sinon.assert.notCalled( sinonTrigger ); - - normalizer.transform( data ); - - sinon.assert.calledOnce( sinonTrigger ); - sinon.assert.calledWith( sinonTrigger, 'test data' ); - } ); - - it( 'should execute filters over data', () => { - const filter = sinon.fake(); - const writer = new UpcastWriter(); - const documentFragment = writer.createDocumentFragment(); - - data.content = documentFragment; - - normalizer.addFilter( filter ); - normalizer.transform( data ); - - sinon.assert.calledOnce( filter ); - sinon.assert.calledWithMatch( filter, { - documentFragment, - data, - writer: sinon.match.instanceOf( UpcastWriter ) - } ); - } ); - - it( 'should not process again already transformed data', () => { - const filter = sinon.fake(); - - // Filters should not be executed - data.isTransformedWithPasteFromOffice = true; - - normalizer.addFilter( filter ); - normalizer.transform( data ); - - sinon.assert.notCalled( filter ); - } ); - } ); - - describe( 'invalid data', () => { - let normalizer, sinonTrigger; - - beforeEach( () => { - sinonTrigger = sinon.fake.returns( false ); - - normalizer = new ContentNormalizer( sinonTrigger ); - } ); - - it( 'should not change data content', () => { - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - expect( data ).to.deep.equal( templateData ); - } ); - - it( 'should not fire any filter', () => { - const filter = sinon.fake(); - - normalizer.addFilter( filter ); - normalizer.transform( data ); - - expect( normalizer._filters.size ).to.equal( 1 ); - sinon.assert.notCalled( filter ); - } ); - } ); - } ); - - describe( 'addFilter()', () => { - let filter; - - beforeEach( () => { - filter = () => {}; - - normalizer.addFilter( filter ); - } ); - - it( 'should add filter to fullContentFilters set', () => { - expect( normalizer._filters.size ).to.equal( 1 ); - - const firstFilter = [ ...normalizer._filters ][ 0 ]; - expect( firstFilter ).to.equal( filter ); - } ); - } ); -} ); diff --git a/tests/normalizers/googledocs.js b/tests/normalizers/googledocs.js index 307bd30..15aeb36 100644 --- a/tests/normalizers/googledocs.js +++ b/tests/normalizers/googledocs.js @@ -3,105 +3,29 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import { googleDocsNormalizer as normalizer } from '../../src/normalizers/googledocs'; -import ContentNormalizer from '../../src/contentnormalizer'; -import { createDataTransfer } from '../_utils/utils'; -import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; -import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import GoogleDocsNormalizer from '../../src/normalizers/googledocs'; -// Functionality of the google docs normalizer is tested with autogenerated normalization tests. +// exec() of the google docs normalizer is tested with autogenerated normalization tests. describe( 'PasteFromOffice/normalizers/googledocs', () => { - const htmlDataProcessor = new HtmlDataProcessor(); + const normalizer = new GoogleDocsNormalizer(); - it( 'should be instance of content normalizers', () => { - expect( normalizer ).to.be.instanceOf( ContentNormalizer ); - } ); - - it( 'should mark data as processed', () => { - const gDocs = '

'; - const data = { - dataTransfer: createDataTransfer( { - 'text/html': gDocs - } ), - content: htmlDataProcessor.toView( gDocs ) - }; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); - - it( 'should not mark non-google-docs data as processed', () => { - const data = { - dataTransfer: createDataTransfer( { 'text/html': 'foo bar' } ) - }; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - } ); - - it( 'outputs view#documentFragment', () => { - const gDocs = '

Foo bar

'; - const data = { - dataTransfer: createDataTransfer( { - 'text/html': gDocs - } ), - content: htmlDataProcessor.toView( gDocs ) - }; - - normalizer.transform( data ); + describe( 'isActive()', () => { + describe( 'correct data set', () => { + it( 'should be active for google docs data', () => { + const gDocs = '

'; - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - expect( data.content ).to.be.instanceOf( DocumentFragment ); - } ); - - describe( 'activation trigger', () => { - describe( 'correct markup', () => { - [ - { - 'text/html': '

Foo bar

' - }, - { - // eslint-disable-next-line max-len - 'text/html': '' - } - ].forEach( ( html, index ) => { - it( `should be active for markup #${ index }`, () => { - const data = { - dataTransfer: createDataTransfer( html ), - content: htmlDataProcessor.toView( html[ 'text/html' ] ) - }; - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); + expect( normalizer.isActive( gDocs ) ).to.be.true; } ); } ); - describe( 'wrong markup', () => { + describe( 'wrong data set', () => { [ - { - 'text/html': '

Hello world

' - }, - { - 'text/html': '' - } - ].forEach( ( html, index ) => { - it( `should be not active for wrong markup #${ index }`, () => { - const data = { - dataTransfer: createDataTransfer( html ), - content: htmlDataProcessor.toView( html[ 'text/html' ] ) - }; - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + '

foo

', + '

Foo bar

', + '' + ].forEach( ( htmlString, index ) => { + it( `should be inactive for: #${ index } data set`, () => { + expect( normalizer.isActive( htmlString ) ).to.be.false; } ); } ); } ); diff --git a/tests/normalizers/msword.js b/tests/normalizers/msword.js index dfd3f51..64f994a 100644 --- a/tests/normalizers/msword.js +++ b/tests/normalizers/msword.js @@ -3,94 +3,31 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import { mswordNormalizer as normalizer } from '../../src/normalizers/msword'; -import ContentNormalizer from '../../src/contentnormalizer'; -import { createDataTransfer } from '../_utils/utils'; -import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragment'; +import MSWordNormalizer from '../../src/normalizers/msword'; -// Functionality of the msword normalizer is tested with autogenerated normalization tests. +// `exec()` of the msword normalizer is tested with autogenerated normalization tests. describe( 'PasteFromOffice/normalizers/msword', () => { - it( 'should be instance of content normalizers', () => { - expect( normalizer ).to.be.instanceOf( ContentNormalizer ); - } ); - - it( 'should mark data as processed', () => { - const data = { - dataTransfer: createDataTransfer( { - 'text/html': '' - } ) - }; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); - - it( 'should not mark non-word data as processed', () => { - const data = { - dataTransfer: createDataTransfer( { 'text/html': 'foo bar' } ) - }; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - } ); - - it( 'outputs view#documentFragment', () => { - const data = { - dataTransfer: createDataTransfer( { - 'text/html': '

Foo bar

' - } ) - }; + const normalizer = new MSWordNormalizer(); - normalizer.transform( data ); - - expect( data.content ).to.be.instanceOf( DocumentFragment ); - } ); - - describe( 'activation trigger', () => { - describe( 'correct markup', () => { + describe( 'isActive()', () => { + describe( 'correct data set', () => { [ - { - 'text/html': '' - }, - { - 'text/html': '' - } - ].forEach( ( html, index ) => { - it( `should be active for markup #${ index }`, () => { - const data = { - dataTransfer: createDataTransfer( html ) - }; - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; + '

Foo bar

', + '' + ].forEach( ( htmlString, index ) => { + it( `should be active for: #${ index } data set`, () => { + expect( normalizer.isActive( htmlString ) ).to.be.true; } ); } ); } ); - describe( 'wrong markup', () => { + describe( 'wrong data set', () => { [ - { - 'text/html': '' - }, - { - 'text/html': '

' - } - ].forEach( ( html, index ) => { - it( `should be not active for wrong markup #${ index }`, () => { - const data = { - dataTransfer: createDataTransfer( html ) - }; - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - - normalizer.transform( data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + '

foo

', + '

' + ].forEach( ( htmlString, index ) => { + it( `should be inactive to for: #${ index } data set`, () => { + expect( normalizer.isActive( htmlString ) ).to.be.false; } ); } ); } ); From c2727b49c9b5970500826ec357f66b6da57690d9 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 26 Jul 2019 09:28:51 +0200 Subject: [PATCH 27/48] Correct folder name with normalizers. --- src/{normalizers => normalizer}/googledocs.js | 0 src/{normalizers => normalizer}/msword.js | 0 src/pastefromoffice.js | 4 ++-- tests/{normalizers => normalizer}/googledocs.js | 2 +- tests/{normalizers => normalizer}/msword.js | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/{normalizers => normalizer}/googledocs.js (100%) rename src/{normalizers => normalizer}/msword.js (100%) rename tests/{normalizers => normalizer}/googledocs.js (93%) rename tests/{normalizers => normalizer}/msword.js (94%) diff --git a/src/normalizers/googledocs.js b/src/normalizer/googledocs.js similarity index 100% rename from src/normalizers/googledocs.js rename to src/normalizer/googledocs.js diff --git a/src/normalizers/msword.js b/src/normalizer/msword.js similarity index 100% rename from src/normalizers/msword.js rename to src/normalizer/msword.js diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index b215e4a..e5dfc4a 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,8 +9,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import GoogleDocsNormalizer from './normalizers/googledocs'; -import MSWordNormalizer from './normalizers/msword'; +import GoogleDocsNormalizer from './normalizer/googledocs'; +import MSWordNormalizer from './normalizer/msword'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; /** diff --git a/tests/normalizers/googledocs.js b/tests/normalizer/googledocs.js similarity index 93% rename from tests/normalizers/googledocs.js rename to tests/normalizer/googledocs.js index 15aeb36..4d65dc3 100644 --- a/tests/normalizers/googledocs.js +++ b/tests/normalizer/googledocs.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import GoogleDocsNormalizer from '../../src/normalizers/googledocs'; +import GoogleDocsNormalizer from '../../src/normalizer/googledocs'; // exec() of the google docs normalizer is tested with autogenerated normalization tests. describe( 'PasteFromOffice/normalizers/googledocs', () => { diff --git a/tests/normalizers/msword.js b/tests/normalizer/msword.js similarity index 94% rename from tests/normalizers/msword.js rename to tests/normalizer/msword.js index 64f994a..e74dd58 100644 --- a/tests/normalizers/msword.js +++ b/tests/normalizer/msword.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import MSWordNormalizer from '../../src/normalizers/msword'; +import MSWordNormalizer from '../../src/normalizer/msword'; // `exec()` of the msword normalizer is tested with autogenerated normalization tests. describe( 'PasteFromOffice/normalizers/msword', () => { From 67d017a42f677fd6cb3ae924936bfcd7d32ff116 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 26 Jul 2019 11:54:00 +0200 Subject: [PATCH 28/48] Add unit test to increase coverage to 100. --- tests/pastefromoffice.js | 87 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index ac664f0..863f9f1 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -6,9 +6,15 @@ import PasteFromOffice from '../src/pastefromoffice'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import { createDataTransfer } from './_utils/utils'; +import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; describe( 'PasteFromOffice', () => { - let editor, pasteFromOffice; + let editor, pasteFromOffice, clipboard; + + testUtils.createSinonSandbox(); + beforeEach( () => { return VirtualTestEditor.create( { plugins: [ PasteFromOffice ] @@ -16,6 +22,7 @@ describe( 'PasteFromOffice', () => { .then( _editor => { editor = _editor; pasteFromOffice = editor.plugins.get( 'PasteFromOffice' ); + clipboard = editor.plugins.get( 'Clipboard' ); } ); } ); @@ -30,4 +37,82 @@ describe( 'PasteFromOffice', () => { it( 'should load Clipboard plugin', () => { expect( editor.plugins.get( Clipboard ) ).to.be.instanceOf( Clipboard ); } ); + + describe( 'isTransformedWithPasteFromOffice - flag', () => { + const htmlDataProcessor = new HtmlDataProcessor(); + + describe( 'data which should be processed', () => { + [ + { + 'text/html': '' + }, + { + 'text/html': '' + }, + { + 'text/html': '

' + } + ].forEach( ( inputData, index ) => { + it( `should mark data as transformed with paste from office - data set: #${ index }`, () => { + const data = { + content: htmlDataProcessor.toView( inputData[ 'text/html' ] ), + dataTransfer: createDataTransfer( inputData ) + }; + + clipboard.fire( 'inputTransformation', data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + } ); + } ); + } ); + + describe( 'not recognized data', () => { + [ + { + 'text/html': '

Hello world

' + }, + { + 'text/html': '' + } + ].forEach( ( inputData, index ) => { + it( `should not modify data set: #${ index }`, () => { + const data = { + content: htmlDataProcessor.toView( inputData[ 'text/html' ] ), + dataTransfer: createDataTransfer( inputData ) + }; + + clipboard.fire( 'inputTransformation', data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + } ); + } ); + } ); + + describe( 'already processed data', () => { + [ + { + // eslint-disable-next-line max-len + 'text/html': '

Hello world

' + }, + { + 'text/html': '

Hello world

' + } + ].forEach( ( inputData, index ) => { + it( `should not modify already processed data: #${ index }`, () => { + const data = { + content: htmlDataProcessor.toView( inputData[ 'text/html' ] ), + dataTransfer: createDataTransfer( inputData ), + isTransformedWithPasteFromOffice: true + }; + + const getData = sinon.spy( data.dataTransfer, 'getData' ); + + clipboard.fire( 'inputTransformation', data ); + + // Data object should not be processed + sinon.assert.notCalled( getData ); + } ); + } ); + } ); + } ); } ); From e3ae28e21da21acac9ac96ed8aa7740e181f8392 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 26 Jul 2019 13:12:30 +0200 Subject: [PATCH 29/48] Improve docs. --- src/normalizer.jsdoc | 16 +++++------ src/normalizer/googledocs.js | 37 ------------------------- src/normalizer/msword.js | 53 ------------------------------------ src/pastefromoffice.js | 4 +-- 4 files changed, 10 insertions(+), 100 deletions(-) delete mode 100644 src/normalizer/googledocs.js delete mode 100644 src/normalizer/msword.js diff --git a/src/normalizer.jsdoc b/src/normalizer.jsdoc index 2f132d6..4677eb2 100644 --- a/src/normalizer.jsdoc +++ b/src/normalizer.jsdoc @@ -8,15 +8,15 @@ */ /** - * Normalizer interface is description of a mechanism which transforms input data send through - * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Input content is transformed - * for data came from office-like applications, for example, : MS Word, Google Docs, etc. These applications generate content, - * which frequently has an invalid HTML syntax. Normalizers detects environment specific quirks and transform it to proper HTML syntax, - * what later might be properly upcast to the {@link module:engine/model/model~Model}. + * Normalizer interface is a description of a mechanism which transforms input data send through + * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Input data is transformed + * for paste event came from office-like applications, for example, MS Word, Google Docs, etc. These applications generate content, + * which frequently has an invalid HTML syntax. Normalizers detects environment-specific quirks and transform it into proper HTML syntax, + * what later might be correctly upcast to the {@link module:engine/model/model~Model}. * - * Content Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance is - * initialized with an activation trigger. Activation trigger is a function which gets content of `text/html` dataTransfer (String) and - * returns `true` or `false`. Based on this result normalizer applies filters to given data. + * Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance has the + * {@link #isActive} method, which checks if given normalizer should be applied for current data content. It is chosen first active + * normalizer for the transformation process. If there is none, then data remain untouched. * * @interface Normalizer */ diff --git a/src/normalizer/googledocs.js b/src/normalizer/googledocs.js deleted file mode 100644 index 66c25aa..0000000 --- a/src/normalizer/googledocs.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module paste-from-office/normalizer - */ - -import { removeBoldTagWrapper } from '../filters/common'; -import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; - -/** - * Normalizer fixing HTML syntax obtained from Google Docs. - * - * @implements module:paste-from-office/normalizer~Normalizer - */ -export default class GoogleDocsNormalizer { - /** - * @inheritDoc - */ - isActive( htmlString ) { - return /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( htmlString ); - } - - /** - * @inheritDoc - */ - exec( data ) { - const writer = new UpcastWriter(); - - removeBoldTagWrapper( { - writer, - documentFragment: data.content - } ); - } -} diff --git a/src/normalizer/msword.js b/src/normalizer/msword.js deleted file mode 100644 index 49faf77..0000000 --- a/src/normalizer/msword.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -/** - * @module paste-from-office/normalizer - */ - -import { parseHtml } from '../filters/parse'; -import { transformListItemLikeElementsIntoLists } from '../filters/list'; -import { replaceImagesSourceWithBase64 } from '../filters/image'; - -/** - * Normalizer fixing HTML syntax obtained from Microsoft Word documents. - * - * @implements module:paste-from-office/normalizer~Normalizer - */ -export default class MSWordNormalizer { - /** - * @inheritDoc - */ - isActive( htmlString ) { - return //i.test( htmlString ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( htmlString ); - } - - /** - * @inheritDoc - */ - exec( data ) { - const html = data.dataTransfer.getData( 'text/html' ); - - data.content = normalizeWordInput( html, data.dataTransfer ); - } -} - -// -// Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. -// -// @private -// @param {String} input Word input. -// @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. -// @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. -// -function normalizeWordInput( input, dataTransfer ) { - const { body, stylesString } = parseHtml( input ); - - transformListItemLikeElementsIntoLists( body, stylesString ); - replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); - - return body; -} diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index e5dfc4a..d675ad7 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -21,8 +21,8 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; * * Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer}. * Currently, there are included followed normalizers: - * * {@link module:paste-from-office/normalizer~MSWordNormalizer MS Word normalizer} - * * {@link module:paste-from-office/normalizer~GoogleDocsNormalizer Google Docs normalizer} + * * {@link module:paste-from-office/normalizer/mswordnormalizer~MSWordNormalizer MS Word normalizer} + * * {@link module:paste-from-office/normalizer/googledocsnormalizer~GoogleDocsNormalizer Google Docs normalizer} * * For more information about this feature check the {@glink api/paste-from-office package page}. * From f5665802551f42e5ccf50f39dae68899bac36efe Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Fri, 26 Jul 2019 14:00:30 +0200 Subject: [PATCH 30/48] Add missing files. --- src/normalizer/googledocsnormalizer.js | 37 ++++++++++++++++++ src/normalizer/mswordnormalizer.js | 53 ++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/normalizer/googledocsnormalizer.js create mode 100644 src/normalizer/mswordnormalizer.js diff --git a/src/normalizer/googledocsnormalizer.js b/src/normalizer/googledocsnormalizer.js new file mode 100644 index 0000000..670118f --- /dev/null +++ b/src/normalizer/googledocsnormalizer.js @@ -0,0 +1,37 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/normalizer/googledocsnormalizer + */ + +import { removeBoldTagWrapper } from '../filters/common'; +import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; + +/** + * Normalizer fixing HTML syntax obtained from Google Docs. + * + * @implements module:paste-from-office/normalizer~Normalizer + */ +export default class GoogleDocsNormalizer { + /** + * @inheritDoc + */ + isActive( htmlString ) { + return /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( htmlString ); + } + + /** + * @inheritDoc + */ + exec( data ) { + const writer = new UpcastWriter(); + + removeBoldTagWrapper( { + writer, + documentFragment: data.content + } ); + } +} diff --git a/src/normalizer/mswordnormalizer.js b/src/normalizer/mswordnormalizer.js new file mode 100644 index 0000000..25bc7ae --- /dev/null +++ b/src/normalizer/mswordnormalizer.js @@ -0,0 +1,53 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +/** + * @module paste-from-office/normalizer/mswordnormalizer + */ + +import { parseHtml } from '../filters/parse'; +import { transformListItemLikeElementsIntoLists } from '../filters/list'; +import { replaceImagesSourceWithBase64 } from '../filters/image'; + +/** + * Normalizer fixing HTML syntax obtained from Microsoft Word documents. + * + * @implements module:paste-from-office/normalizer~Normalizer + */ +export default class MSWordNormalizer { + /** + * @inheritDoc + */ + isActive( htmlString ) { + return //i.test( htmlString ) || + /xmlns:o="urn:schemas-microsoft-com/i.test( htmlString ); + } + + /** + * @inheritDoc + */ + exec( data ) { + const html = data.dataTransfer.getData( 'text/html' ); + + data.content = normalizeWordInput( html, data.dataTransfer ); + } +} + +// +// Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. +// +// @private +// @param {String} input Word input. +// @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. +// @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. +// +function normalizeWordInput( input, dataTransfer ) { + const { body, stylesString } = parseHtml( input ); + + transformListItemLikeElementsIntoLists( body, stylesString ); + replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); + + return body; +} From df6b1b8d1015291a34ff56b0ebe6f987f98eb549 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 09:49:42 +0200 Subject: [PATCH 31/48] Correct import statements. --- src/normalizer.jsdoc | 2 +- src/pastefromoffice.js | 4 ++-- tests/normalizer/googledocs.js | 4 ++-- tests/normalizer/msword.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/normalizer.jsdoc b/src/normalizer.jsdoc index 4677eb2..976477b 100644 --- a/src/normalizer.jsdoc +++ b/src/normalizer.jsdoc @@ -11,7 +11,7 @@ * Normalizer interface is a description of a mechanism which transforms input data send through * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Input data is transformed * for paste event came from office-like applications, for example, MS Word, Google Docs, etc. These applications generate content, - * which frequently has an invalid HTML syntax. Normalizers detects environment-specific quirks and transform it into proper HTML syntax, + * which frequently has an invalid HTML syntax. Normalizers detect environment-specific quirks and transform it into proper HTML syntax, * what later might be correctly upcast to the {@link module:engine/model/model~Model}. * * Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance has the diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index d675ad7..eb6a6c3 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,8 +9,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import GoogleDocsNormalizer from './normalizer/googledocs'; -import MSWordNormalizer from './normalizer/msword'; +import GoogleDocsNormalizer from './normalizer/googledocsnormalizer'; +import MSWordNormalizer from './normalizer/mswordnormalizer'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; /** diff --git a/tests/normalizer/googledocs.js b/tests/normalizer/googledocs.js index 4d65dc3..f910ea5 100644 --- a/tests/normalizer/googledocs.js +++ b/tests/normalizer/googledocs.js @@ -3,10 +3,10 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import GoogleDocsNormalizer from '../../src/normalizer/googledocs'; +import GoogleDocsNormalizer from '../../src/normalizer/googledocsnormalizer'; // exec() of the google docs normalizer is tested with autogenerated normalization tests. -describe( 'PasteFromOffice/normalizers/googledocs', () => { +describe( 'PasteFromOffice/normalizer/googledocsnormalizer', () => { const normalizer = new GoogleDocsNormalizer(); describe( 'isActive()', () => { diff --git a/tests/normalizer/msword.js b/tests/normalizer/msword.js index e74dd58..f9dd74c 100644 --- a/tests/normalizer/msword.js +++ b/tests/normalizer/msword.js @@ -3,10 +3,10 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import MSWordNormalizer from '../../src/normalizer/msword'; +import MSWordNormalizer from '../../src/normalizer/mswordnormalizer'; // `exec()` of the msword normalizer is tested with autogenerated normalization tests. -describe( 'PasteFromOffice/normalizers/msword', () => { +describe( 'PasteFromOffice/normalizer/mswornormalizer', () => { const normalizer = new MSWordNormalizer(); describe( 'isActive()', () => { From 2ae8f69c79ee3c261e637c7f0fa37b6d89de44a9 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 09:50:17 +0200 Subject: [PATCH 32/48] Rename unit test to match src files. --- tests/normalizer/{googledocs.js => googledocsnormalizer.js} | 0 tests/normalizer/{msword.js => mswordnormalizer.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/normalizer/{googledocs.js => googledocsnormalizer.js} (100%) rename tests/normalizer/{msword.js => mswordnormalizer.js} (100%) diff --git a/tests/normalizer/googledocs.js b/tests/normalizer/googledocsnormalizer.js similarity index 100% rename from tests/normalizer/googledocs.js rename to tests/normalizer/googledocsnormalizer.js diff --git a/tests/normalizer/msword.js b/tests/normalizer/mswordnormalizer.js similarity index 100% rename from tests/normalizer/msword.js rename to tests/normalizer/mswordnormalizer.js From ac1c0a166d98e5aedcdcf3408064b4369b528498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Go=C5=82aszewski?= Date: Mon, 29 Jul 2019 11:45:31 +0200 Subject: [PATCH 33/48] Update normalizer documentation. --- src/normalizer.jsdoc | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/normalizer.jsdoc b/src/normalizer.jsdoc index 976477b..beac521 100644 --- a/src/normalizer.jsdoc +++ b/src/normalizer.jsdoc @@ -8,22 +8,17 @@ */ /** - * Normalizer interface is a description of a mechanism which transforms input data send through - * an {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. Input data is transformed - * for paste event came from office-like applications, for example, MS Word, Google Docs, etc. These applications generate content, - * which frequently has an invalid HTML syntax. Normalizers detect environment-specific quirks and transform it into proper HTML syntax, - * what later might be correctly upcast to the {@link module:engine/model/model~Model}. + * Interface defining a content transformation pasted from an external editor. * - * Normalizers are registered by {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin. Each instance has the - * {@link #isActive} method, which checks if given normalizer should be applied for current data content. It is chosen first active - * normalizer for the transformation process. If there is none, then data remain untouched. + * Normalizers are registered by the {@link module:paste-from-office/pastefromoffice~PasteFromOffice} plugin and run on + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. They detect environment-specific + * quirks and transform it into a form compatible with other CKEditor features. * * @interface Normalizer */ /** - * Method determines if current normalizer should be run for given content. It takes an HTML string from `data.dataTransfer` as an argument - * and it should return a boolean value. + * Must return `true` if the `htmlString` contains content which this normalizer can transform. * * @method #isActive * @param {String} htmlString full content of `dataTransfer.getData( 'text/html' )` @@ -31,9 +26,9 @@ */ /** - * Method applies normalization to given data. + * Executes the normalization of a given data. * * @method #exec * @param {Object} data object obtained from - * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event} + * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. */ From 84762a8625d6c5c20c0ab8edba251125d71505c5 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:16:18 +0200 Subject: [PATCH 34/48] Apply suggestions from code review Co-Authored-By: Maciej --- src/normalizer/googledocsnormalizer.js | 2 +- src/normalizer/mswordnormalizer.js | 2 +- src/pastefromoffice.js | 6 +++--- tests/data/integration.js | 2 +- tests/data/normalization.js | 2 +- tests/filters/common.js | 2 +- tests/filters/image.js | 2 +- tests/filters/list.js | 2 +- tests/filters/parse.js | 2 +- tests/filters/space.js | 2 +- tests/normalizer/googledocsnormalizer.js | 2 +- tests/normalizer/mswordnormalizer.js | 2 +- tests/pastefromoffice.js | 4 ++-- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/normalizer/googledocsnormalizer.js b/src/normalizer/googledocsnormalizer.js index 670118f..59bca45 100644 --- a/src/normalizer/googledocsnormalizer.js +++ b/src/normalizer/googledocsnormalizer.js @@ -11,7 +11,7 @@ import { removeBoldTagWrapper } from '../filters/common'; import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; /** - * Normalizer fixing HTML syntax obtained from Google Docs. + * Normalizer for the content pasted from Google Docs. * * @implements module:paste-from-office/normalizer~Normalizer */ diff --git a/src/normalizer/mswordnormalizer.js b/src/normalizer/mswordnormalizer.js index 25bc7ae..a0b6a8d 100644 --- a/src/normalizer/mswordnormalizer.js +++ b/src/normalizer/mswordnormalizer.js @@ -12,7 +12,7 @@ import { transformListItemLikeElementsIntoLists } from '../filters/list'; import { replaceImagesSourceWithBase64 } from '../filters/image'; /** - * Normalizer fixing HTML syntax obtained from Microsoft Word documents. + * Normalizer for the content pasted from Microsoft Word. * * @implements module:paste-from-office/normalizer~Normalizer */ diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index eb6a6c3..e89baea 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -19,9 +19,9 @@ import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; * This plugin handles content pasted from Office apps and transforms it (if necessary) * to a valid structure which can then be understood by the editor features. * - * Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer}. - * Currently, there are included followed normalizers: - * * {@link module:paste-from-office/normalizer/mswordnormalizer~MSWordNormalizer MS Word normalizer} + * Transformation is made by a set of predefined {@link module:paste-from-office/normalizer~Normalizer normalizers}. + * This plugin includes following normalizers: + * * {@link module:paste-from-office/normalizer/mswordnormalizer~MSWordNormalizer Microsoft Word normalizer} * * {@link module:paste-from-office/normalizer/googledocsnormalizer~GoogleDocsNormalizer Google Docs normalizer} * * For more information about this feature check the {@glink api/paste-from-office package page}. diff --git a/tests/data/integration.js b/tests/data/integration.js index 388dc01..7c00a1e 100644 --- a/tests/data/integration.js +++ b/tests/data/integration.js @@ -22,7 +22,7 @@ import { generateTests } from '../_utils/utils'; const browsers = [ 'chrome', 'firefox', 'safari', 'edge' ]; -describe( 'PasteFromOffice/data - automatic', () => { +describe( 'PasteFromOffice - integration', () => { generateTests( { input: 'basic-styles', type: 'integration', diff --git a/tests/data/normalization.js b/tests/data/normalization.js index 728b932..48210ff 100644 --- a/tests/data/normalization.js +++ b/tests/data/normalization.js @@ -14,7 +14,7 @@ const editorConfig = { plugins: [ Clipboard, PasteFromOffice ] }; -describe( 'PasteFromOffice/data - automatic', () => { +describe( 'PasteFromOffice - normalization', () => { generateTests( { input: 'basic-styles', type: 'normalization', diff --git a/tests/filters/common.js b/tests/filters/common.js index c98dacf..ee8d7c0 100644 --- a/tests/filters/common.js +++ b/tests/filters/common.js @@ -7,7 +7,7 @@ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/html import { removeBoldTagWrapper } from '../../src/filters/common'; import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; -describe( 'PasteFromOffice/filters', () => { +describe( 'PasteFromOffice - filters', () => { const htmlDataProcessor = new HtmlDataProcessor(); describe( 'common', () => { describe( 'removeBoldTagWrapper', () => { diff --git a/tests/filters/image.js b/tests/filters/image.js index 542114f..f28391c 100644 --- a/tests/filters/image.js +++ b/tests/filters/image.js @@ -12,7 +12,7 @@ import { parseHtml } from '../../src/filters/parse'; import { replaceImagesSourceWithBase64, _convertHexToBase64 } from '../../src/filters/image'; import { browserFixtures } from '../_data/image/index'; -describe( 'PasteFromOffice/filters', () => { +describe( 'PasteFromOffice - filters', () => { describe( 'image', () => { let editor; diff --git a/tests/filters/list.js b/tests/filters/list.js index 1a1c95a..5c3fb01 100644 --- a/tests/filters/list.js +++ b/tests/filters/list.js @@ -9,7 +9,7 @@ import View from '@ckeditor/ckeditor5-engine/src/view/view'; import { transformListItemLikeElementsIntoLists } from '../../src/filters/list'; -describe( 'PasteFromOffice/filters', () => { +describe( 'PasteFromOffice - filters', () => { describe( 'list', () => { const htmlDataProcessor = new HtmlDataProcessor(); diff --git a/tests/filters/parse.js b/tests/filters/parse.js index 13dadf7..4f35a7d 100644 --- a/tests/filters/parse.js +++ b/tests/filters/parse.js @@ -9,7 +9,7 @@ import DocumentFragment from '@ckeditor/ckeditor5-engine/src/view/documentfragme import { parseHtml } from '../../src/filters/parse'; -describe( 'PasteFromOffice/filters', () => { +describe( 'PasteFromOffice - filters', () => { describe( 'parse', () => { describe( 'parseHtml()', () => { it( 'correctly parses HTML with body and one style tag', () => { diff --git a/tests/filters/space.js b/tests/filters/space.js index 102f1db..8898d03 100644 --- a/tests/filters/space.js +++ b/tests/filters/space.js @@ -7,7 +7,7 @@ import { normalizeSpacing, normalizeSpacerunSpans } from '../../src/filters/space'; -describe( 'PasteFromOffice/filters', () => { +describe( 'PasteFromOffice - filters', () => { describe( 'space', () => { describe( 'normalizeSpacing()', () => { it( 'should replace last space before closing tag with NBSP', () => { diff --git a/tests/normalizer/googledocsnormalizer.js b/tests/normalizer/googledocsnormalizer.js index f910ea5..573876d 100644 --- a/tests/normalizer/googledocsnormalizer.js +++ b/tests/normalizer/googledocsnormalizer.js @@ -6,7 +6,7 @@ import GoogleDocsNormalizer from '../../src/normalizer/googledocsnormalizer'; // exec() of the google docs normalizer is tested with autogenerated normalization tests. -describe( 'PasteFromOffice/normalizer/googledocsnormalizer', () => { +describe( 'GoogleDocsNormalizer', () => { const normalizer = new GoogleDocsNormalizer(); describe( 'isActive()', () => { diff --git a/tests/normalizer/mswordnormalizer.js b/tests/normalizer/mswordnormalizer.js index f9dd74c..90d7fc5 100644 --- a/tests/normalizer/mswordnormalizer.js +++ b/tests/normalizer/mswordnormalizer.js @@ -6,7 +6,7 @@ import MSWordNormalizer from '../../src/normalizer/mswordnormalizer'; // `exec()` of the msword normalizer is tested with autogenerated normalization tests. -describe( 'PasteFromOffice/normalizer/mswornormalizer', () => { +describe( 'MSWordNormalizer', () => { const normalizer = new MSWordNormalizer(); describe( 'isActive()', () => { diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 863f9f1..f736b81 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -26,11 +26,11 @@ describe( 'PasteFromOffice', () => { } ); } ); - it( 'is Paste from Office', () => { + it( 'should be loaded', () => { expect( pasteFromOffice ).to.be.instanceOf( PasteFromOffice ); } ); - it( 'should have static name', () => { + it( 'has proper name', () => { expect( PasteFromOffice.pluginName ).to.equal( 'PasteFromOffice' ); } ); From 7eaee980d391bece5b1cc95a65a775cd1c1b5f84 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 13:55:20 +0200 Subject: [PATCH 35/48] Change namespace of removeBoldTagWrapper filter. --- .../{common.js => removeboldtagwrapper.js} | 4 +- src/normalizer/googledocsnormalizer.js | 2 +- tests/filters/common.js | 52 ------------------- tests/filters/reomoveboldtagwrapper.js | 50 ++++++++++++++++++ 4 files changed, 53 insertions(+), 55 deletions(-) rename src/filters/{common.js => removeboldtagwrapper.js} (84%) delete mode 100644 tests/filters/common.js create mode 100644 tests/filters/reomoveboldtagwrapper.js diff --git a/src/filters/common.js b/src/filters/removeboldtagwrapper.js similarity index 84% rename from src/filters/common.js rename to src/filters/removeboldtagwrapper.js index 1e77698..6672cfe 100644 --- a/src/filters/common.js +++ b/src/filters/removeboldtagwrapper.js @@ -4,7 +4,7 @@ */ /** - * @module paste-from-office/filters/common + * @module paste-from-office/filters/removeboldtagwrapper */ /** @@ -12,7 +12,7 @@ * * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment */ -export function removeBoldTagWrapper( { documentFragment, writer } ) { +export default function removeBoldTagWrapper( { documentFragment, writer } ) { for ( const childWithWrapper of documentFragment.getChildren() ) { if ( childWithWrapper.is( 'b' ) && childWithWrapper.getStyle( 'font-weight' ) === 'normal' ) { const childIndex = documentFragment.getChildIndex( childWithWrapper ); diff --git a/src/normalizer/googledocsnormalizer.js b/src/normalizer/googledocsnormalizer.js index 59bca45..b91158a 100644 --- a/src/normalizer/googledocsnormalizer.js +++ b/src/normalizer/googledocsnormalizer.js @@ -7,7 +7,7 @@ * @module paste-from-office/normalizer/googledocsnormalizer */ -import { removeBoldTagWrapper } from '../filters/common'; +import removeBoldTagWrapper from '../filters/removeboldtagwrapper'; import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; /** diff --git a/tests/filters/common.js b/tests/filters/common.js deleted file mode 100644 index ee8d7c0..0000000 --- a/tests/filters/common.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license - */ - -import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; -import { removeBoldTagWrapper } from '../../src/filters/common'; -import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; - -describe( 'PasteFromOffice - filters', () => { - const htmlDataProcessor = new HtmlDataProcessor(); - describe( 'common', () => { - describe( 'removeBoldTagWrapper', () => { - let writer; - - before( () => { - writer = new UpcastWriter(); - } ); - - it( 'should remove bold wrapper added by google docs', () => { - const inputData = '' + - '

Hello world

' + - '
'; - const documentFragment = htmlDataProcessor.toView( inputData ); - - removeBoldTagWrapper( { documentFragment, writer } ); - - expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); - } ); - - it( 'should not remove non-bold tag with google id', () => { - const inputData = '

Hello world

'; - const documentFragment = htmlDataProcessor.toView( inputData ); - - removeBoldTagWrapper( { documentFragment, writer } ); - - expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( - '

Hello world

' ); - } ); - - it( 'should not remove bold tag without google id', () => { - const inputData = 'Hello world'; - const documentFragment = htmlDataProcessor.toView( inputData ); - - removeBoldTagWrapper( { documentFragment, writer } ); - - expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( - 'Hello world' ); - } ); - } ); - } ); -} ); diff --git a/tests/filters/reomoveboldtagwrapper.js b/tests/filters/reomoveboldtagwrapper.js new file mode 100644 index 0000000..cdd16ab --- /dev/null +++ b/tests/filters/reomoveboldtagwrapper.js @@ -0,0 +1,50 @@ +/** + * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + */ + +import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; +import removeBoldTagWrapper from '../../src/filters/removeboldtagwrapper'; +import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; + +describe( 'PasteFromOffice - filters', () => { + const htmlDataProcessor = new HtmlDataProcessor(); + describe( 'removeBoldTagWrapper', () => { + let writer; + + before( () => { + writer = new UpcastWriter(); + } ); + + it( 'should remove bold wrapper added by google docs', () => { + const inputData = '' + + '

Hello world

' + + '
'; + const documentFragment = htmlDataProcessor.toView( inputData ); + + removeBoldTagWrapper( { documentFragment, writer } ); + + expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); + } ); + + it( 'should not remove non-bold tag with google id', () => { + const inputData = '

Hello world

'; + const documentFragment = htmlDataProcessor.toView( inputData ); + + removeBoldTagWrapper( { documentFragment, writer } ); + + expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( + '

Hello world

' ); + } ); + + it( 'should not remove bold tag without google id', () => { + const inputData = 'Hello world'; + const documentFragment = htmlDataProcessor.toView( inputData ); + + removeBoldTagWrapper( { documentFragment, writer } ); + + expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( + 'Hello world' ); + } ); + } ); +} ); From 219aac750767bb3c4cda148f64aa1497d11ad669 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:02:27 +0200 Subject: [PATCH 36/48] Improve fitler styling. --- src/filters/removeboldtagwrapper.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/filters/removeboldtagwrapper.js b/src/filters/removeboldtagwrapper.js index 6672cfe..a71e89e 100644 --- a/src/filters/removeboldtagwrapper.js +++ b/src/filters/removeboldtagwrapper.js @@ -13,12 +13,12 @@ * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment */ export default function removeBoldTagWrapper( { documentFragment, writer } ) { - for ( const childWithWrapper of documentFragment.getChildren() ) { - if ( childWithWrapper.is( 'b' ) && childWithWrapper.getStyle( 'font-weight' ) === 'normal' ) { - const childIndex = documentFragment.getChildIndex( childWithWrapper ); - const removedElement = writer.remove( childWithWrapper )[ 0 ]; + for ( const child of documentFragment.getChildren() ) { + if ( child.is( 'b' ) && child.getStyle( 'font-weight' ) === 'normal' ) { + const childIndex = documentFragment.getChildIndex( child ); - writer.insertChild( childIndex, removedElement.getChildren(), documentFragment ); + writer.remove( child ); + writer.insertChild( childIndex, child.getChildren(), documentFragment ); } } } From dd1fa0df1f4700e6c4832d4f82b8f7f20fe27be4 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:02:52 +0200 Subject: [PATCH 37/48] Rename exec to execute. --- src/normalizer.jsdoc | 2 +- src/normalizer/googledocsnormalizer.js | 2 +- src/normalizer/mswordnormalizer.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/normalizer.jsdoc b/src/normalizer.jsdoc index beac521..51f6bb3 100644 --- a/src/normalizer.jsdoc +++ b/src/normalizer.jsdoc @@ -28,7 +28,7 @@ /** * Executes the normalization of a given data. * - * @method #exec + * @method #execute * @param {Object} data object obtained from * {@link module:clipboard/clipboard~Clipboard#event:inputTransformation inputTransformation event}. */ diff --git a/src/normalizer/googledocsnormalizer.js b/src/normalizer/googledocsnormalizer.js index b91158a..57eed5d 100644 --- a/src/normalizer/googledocsnormalizer.js +++ b/src/normalizer/googledocsnormalizer.js @@ -26,7 +26,7 @@ export default class GoogleDocsNormalizer { /** * @inheritDoc */ - exec( data ) { + execute( data ) { const writer = new UpcastWriter(); removeBoldTagWrapper( { diff --git a/src/normalizer/mswordnormalizer.js b/src/normalizer/mswordnormalizer.js index a0b6a8d..ca80af4 100644 --- a/src/normalizer/mswordnormalizer.js +++ b/src/normalizer/mswordnormalizer.js @@ -28,7 +28,7 @@ export default class MSWordNormalizer { /** * @inheritDoc */ - exec( data ) { + execute( data ) { const html = data.dataTransfer.getData( 'text/html' ); data.content = normalizeWordInput( html, data.dataTransfer ); From 1b9829cf5ff9cfa01b2d1711fca52a28469c13fc Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:07:12 +0200 Subject: [PATCH 38/48] Rename removeBoldTagWraper to removeBoldWrapper. --- .../{removeboldtagwrapper.js => removeboldwrapper.js} | 2 +- src/normalizer/googledocsnormalizer.js | 7 ++----- ...{reomoveboldtagwrapper.js => reomoveboldwrapper.js} | 10 +++++----- 3 files changed, 8 insertions(+), 11 deletions(-) rename src/filters/{removeboldtagwrapper.js => removeboldwrapper.js} (90%) rename tests/filters/{reomoveboldtagwrapper.js => reomoveboldwrapper.js} (84%) diff --git a/src/filters/removeboldtagwrapper.js b/src/filters/removeboldwrapper.js similarity index 90% rename from src/filters/removeboldtagwrapper.js rename to src/filters/removeboldwrapper.js index a71e89e..8411a32 100644 --- a/src/filters/removeboldtagwrapper.js +++ b/src/filters/removeboldwrapper.js @@ -12,7 +12,7 @@ * * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment */ -export default function removeBoldTagWrapper( { documentFragment, writer } ) { +export default function removeBoldWrapper( documentFragment, writer ) { for ( const child of documentFragment.getChildren() ) { if ( child.is( 'b' ) && child.getStyle( 'font-weight' ) === 'normal' ) { const childIndex = documentFragment.getChildIndex( child ); diff --git a/src/normalizer/googledocsnormalizer.js b/src/normalizer/googledocsnormalizer.js index 57eed5d..e30ccad 100644 --- a/src/normalizer/googledocsnormalizer.js +++ b/src/normalizer/googledocsnormalizer.js @@ -7,7 +7,7 @@ * @module paste-from-office/normalizer/googledocsnormalizer */ -import removeBoldTagWrapper from '../filters/removeboldtagwrapper'; +import removeBoldWrapper from '../filters/removeboldwrapper'; import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; /** @@ -29,9 +29,6 @@ export default class GoogleDocsNormalizer { execute( data ) { const writer = new UpcastWriter(); - removeBoldTagWrapper( { - writer, - documentFragment: data.content - } ); + removeBoldWrapper( data.content, writer ); } } diff --git a/tests/filters/reomoveboldtagwrapper.js b/tests/filters/reomoveboldwrapper.js similarity index 84% rename from tests/filters/reomoveboldtagwrapper.js rename to tests/filters/reomoveboldwrapper.js index cdd16ab..db35333 100644 --- a/tests/filters/reomoveboldtagwrapper.js +++ b/tests/filters/reomoveboldwrapper.js @@ -4,12 +4,12 @@ */ import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'; -import removeBoldTagWrapper from '../../src/filters/removeboldtagwrapper'; +import removeBoldWrapper from '../../src/filters/removeboldwrapper'; import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; describe( 'PasteFromOffice - filters', () => { const htmlDataProcessor = new HtmlDataProcessor(); - describe( 'removeBoldTagWrapper', () => { + describe( 'removeBoldWrapper', () => { let writer; before( () => { @@ -22,7 +22,7 @@ describe( 'PasteFromOffice - filters', () => { '
'; const documentFragment = htmlDataProcessor.toView( inputData ); - removeBoldTagWrapper( { documentFragment, writer } ); + removeBoldWrapper( documentFragment, writer ); expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); } ); @@ -31,7 +31,7 @@ describe( 'PasteFromOffice - filters', () => { const inputData = '

Hello world

'; const documentFragment = htmlDataProcessor.toView( inputData ); - removeBoldTagWrapper( { documentFragment, writer } ); + removeBoldWrapper( documentFragment, writer ); expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( '

Hello world

' ); @@ -41,7 +41,7 @@ describe( 'PasteFromOffice - filters', () => { const inputData = 'Hello world'; const documentFragment = htmlDataProcessor.toView( inputData ); - removeBoldTagWrapper( { documentFragment, writer } ); + removeBoldWrapper( documentFragment, writer ); expect( htmlDataProcessor.toData( documentFragment ) ).to.equal( 'Hello world' ); From 614005a338c7a2a6193154073811ecdf12902b92 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:07:43 +0200 Subject: [PATCH 39/48] Fix not renamed exec statement. --- src/pastefromoffice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index e89baea..19d8ca8 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -64,7 +64,7 @@ export default class PasteFromOffice extends Plugin { const activeNormalizer = normalizers.find( normalizer => normalizer.isActive( htmlString ) ); if ( activeNormalizer ) { - activeNormalizer.exec( data ); + activeNormalizer.execute( data ); data.isTransformedWithPasteFromOffice = true; } From 11ae6a10530d0bf68e913bc4eda11a6921bc848c Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:10:23 +0200 Subject: [PATCH 40/48] Fix docs description. --- src/filters/removeboldwrapper.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/filters/removeboldwrapper.js b/src/filters/removeboldwrapper.js index 8411a32..9bf8f0f 100644 --- a/src/filters/removeboldwrapper.js +++ b/src/filters/removeboldwrapper.js @@ -10,7 +10,8 @@ /** * Removes `` tag wrapper added by Google Docs to a copied content. * - * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment + * @param {module:engine/view/documentfragment~DocumentFragment} documentFragment element `data.content` obtained from clipboard + * @param {module:engine/view/upcastwriter~UpcastWriter} writer */ export default function removeBoldWrapper( documentFragment, writer ) { for ( const child of documentFragment.getChildren() ) { From 8bc14fed8409afff778831bec93cebefc1b5ed29 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:26:08 +0200 Subject: [PATCH 41/48] Rename normalizer to normalizers namespace. --- src/{normalizer => normalizers}/googledocsnormalizer.js | 2 +- src/{normalizer => normalizers}/mswordnormalizer.js | 2 +- src/pastefromoffice.js | 4 ++-- tests/{normalizer => normalizers}/googledocsnormalizer.js | 2 +- tests/{normalizer => normalizers}/mswordnormalizer.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename src/{normalizer => normalizers}/googledocsnormalizer.js (92%) rename src/{normalizer => normalizers}/mswordnormalizer.js (96%) rename tests/{normalizer => normalizers}/googledocsnormalizer.js (92%) rename tests/{normalizer => normalizers}/mswordnormalizer.js (93%) diff --git a/src/normalizer/googledocsnormalizer.js b/src/normalizers/googledocsnormalizer.js similarity index 92% rename from src/normalizer/googledocsnormalizer.js rename to src/normalizers/googledocsnormalizer.js index e30ccad..9d7d7f4 100644 --- a/src/normalizer/googledocsnormalizer.js +++ b/src/normalizers/googledocsnormalizer.js @@ -4,7 +4,7 @@ */ /** - * @module paste-from-office/normalizer/googledocsnormalizer + * @module paste-from-office/normalizers/googledocsnormalizer */ import removeBoldWrapper from '../filters/removeboldwrapper'; diff --git a/src/normalizer/mswordnormalizer.js b/src/normalizers/mswordnormalizer.js similarity index 96% rename from src/normalizer/mswordnormalizer.js rename to src/normalizers/mswordnormalizer.js index ca80af4..c84beea 100644 --- a/src/normalizer/mswordnormalizer.js +++ b/src/normalizers/mswordnormalizer.js @@ -4,7 +4,7 @@ */ /** - * @module paste-from-office/normalizer/mswordnormalizer + * @module paste-from-office/normalizers/mswordnormalizer */ import { parseHtml } from '../filters/parse'; diff --git a/src/pastefromoffice.js b/src/pastefromoffice.js index 19d8ca8..bdd89d6 100644 --- a/src/pastefromoffice.js +++ b/src/pastefromoffice.js @@ -9,8 +9,8 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import GoogleDocsNormalizer from './normalizer/googledocsnormalizer'; -import MSWordNormalizer from './normalizer/mswordnormalizer'; +import GoogleDocsNormalizer from './normalizers/googledocsnormalizer'; +import MSWordNormalizer from './normalizers/mswordnormalizer'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; /** diff --git a/tests/normalizer/googledocsnormalizer.js b/tests/normalizers/googledocsnormalizer.js similarity index 92% rename from tests/normalizer/googledocsnormalizer.js rename to tests/normalizers/googledocsnormalizer.js index 573876d..ef5d068 100644 --- a/tests/normalizer/googledocsnormalizer.js +++ b/tests/normalizers/googledocsnormalizer.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import GoogleDocsNormalizer from '../../src/normalizer/googledocsnormalizer'; +import GoogleDocsNormalizer from '../../src/normalizers/googledocsnormalizer'; // exec() of the google docs normalizer is tested with autogenerated normalization tests. describe( 'GoogleDocsNormalizer', () => { diff --git a/tests/normalizer/mswordnormalizer.js b/tests/normalizers/mswordnormalizer.js similarity index 93% rename from tests/normalizer/mswordnormalizer.js rename to tests/normalizers/mswordnormalizer.js index 90d7fc5..9bdbef4 100644 --- a/tests/normalizer/mswordnormalizer.js +++ b/tests/normalizers/mswordnormalizer.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -import MSWordNormalizer from '../../src/normalizer/mswordnormalizer'; +import MSWordNormalizer from '../../src/normalizers/mswordnormalizer'; // `exec()` of the msword normalizer is tested with autogenerated normalization tests. describe( 'MSWordNormalizer', () => { From 201e07cd6c4bc7de81940d6f8fed65693a965d1c Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 14:36:47 +0200 Subject: [PATCH 42/48] Unify google docs and ms word normalizers. --- src/normalizers/mswordnormalizer.js | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/normalizers/mswordnormalizer.js b/src/normalizers/mswordnormalizer.js index c84beea..63238a7 100644 --- a/src/normalizers/mswordnormalizer.js +++ b/src/normalizers/mswordnormalizer.js @@ -29,25 +29,11 @@ export default class MSWordNormalizer { * @inheritDoc */ execute( data ) { - const html = data.dataTransfer.getData( 'text/html' ); + const { body, stylesString } = parseHtml( data.dataTransfer.getData( 'text/html' ) ); - data.content = normalizeWordInput( html, data.dataTransfer ); - } -} - -// -// Normalizes input pasted from Word to format suitable for editor {@link module:engine/model/model~Model}. -// -// @private -// @param {String} input Word input. -// @param {module:clipboard/datatransfer~DataTransfer} dataTransfer Data transfer instance. -// @returns {module:engine/view/documentfragment~DocumentFragment} Normalized input. -// -function normalizeWordInput( input, dataTransfer ) { - const { body, stylesString } = parseHtml( input ); + transformListItemLikeElementsIntoLists( body, stylesString ); + replaceImagesSourceWithBase64( body, data.dataTransfer.getData( 'text/rtf' ) ); - transformListItemLikeElementsIntoLists( body, stylesString ); - replaceImagesSourceWithBase64( body, dataTransfer.getData( 'text/rtf' ) ); - - return body; + data.content = body; + } } From b055de3f1204333c6b9d849bd462cceba24c401d Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 15:11:49 +0200 Subject: [PATCH 43/48] Extract regexps matching content source to separate constant variables. --- src/normalizers/googledocsnormalizer.js | 4 +++- src/normalizers/mswordnormalizer.js | 6 ++++-- tests/pastefromoffice.js | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/normalizers/googledocsnormalizer.js b/src/normalizers/googledocsnormalizer.js index 9d7d7f4..9599e72 100644 --- a/src/normalizers/googledocsnormalizer.js +++ b/src/normalizers/googledocsnormalizer.js @@ -10,6 +10,8 @@ import removeBoldWrapper from '../filters/removeboldwrapper'; import UpcastWriter from '@ckeditor/ckeditor5-engine/src/view/upcastwriter'; +const googleDocsMatch = /id=("|')docs-internal-guid-[-0-9a-f]+("|')/i; + /** * Normalizer for the content pasted from Google Docs. * @@ -20,7 +22,7 @@ export default class GoogleDocsNormalizer { * @inheritDoc */ isActive( htmlString ) { - return /id=("|')docs-internal-guid-[-0-9a-f]+("|')/.test( htmlString ); + return googleDocsMatch.test( htmlString ); } /** diff --git a/src/normalizers/mswordnormalizer.js b/src/normalizers/mswordnormalizer.js index 63238a7..1bb8a7a 100644 --- a/src/normalizers/mswordnormalizer.js +++ b/src/normalizers/mswordnormalizer.js @@ -11,6 +11,9 @@ import { parseHtml } from '../filters/parse'; import { transformListItemLikeElementsIntoLists } from '../filters/list'; import { replaceImagesSourceWithBase64 } from '../filters/image'; +const msWordMatch1 = //i; +const msWordMatch2 = /xmlns:o="urn:schemas-microsoft-com/i; + /** * Normalizer for the content pasted from Microsoft Word. * @@ -21,8 +24,7 @@ export default class MSWordNormalizer { * @inheritDoc */ isActive( htmlString ) { - return //i.test( htmlString ) || - /xmlns:o="urn:schemas-microsoft-com/i.test( htmlString ); + return msWordMatch1.test( htmlString ) || msWordMatch2.test( htmlString ); } /** diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index f736b81..2fed2dc 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -91,8 +91,8 @@ describe( 'PasteFromOffice', () => { describe( 'already processed data', () => { [ { - // eslint-disable-next-line max-len - 'text/html': '

Hello world

' + 'text/html': '

Hello world

' }, { 'text/html': '

Hello world

' From 1bbdbd1dc405ca316cf1251b00b69b4cff3d9da0 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 15:51:53 +0200 Subject: [PATCH 44/48] Replace foreach loop with help funciton in pastefromoffice tests. --- tests/pastefromoffice.js | 124 +++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 70 deletions(-) diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 2fed2dc..8eac576 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -11,6 +11,7 @@ import { createDataTransfer } from './_utils/utils'; import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils'; describe( 'PasteFromOffice', () => { + const htmlDataProcessor = new HtmlDataProcessor(); let editor, pasteFromOffice, clipboard; testUtils.createSinonSandbox(); @@ -39,80 +40,63 @@ describe( 'PasteFromOffice', () => { } ); describe( 'isTransformedWithPasteFromOffice - flag', () => { - const htmlDataProcessor = new HtmlDataProcessor(); - - describe( 'data which should be processed', () => { - [ - { - 'text/html': '' - }, - { - 'text/html': '' - }, - { - 'text/html': '

' - } - ].forEach( ( inputData, index ) => { - it( `should mark data as transformed with paste from office - data set: #${ index }`, () => { - const data = { - content: htmlDataProcessor.toView( inputData[ 'text/html' ] ), - dataTransfer: createDataTransfer( inputData ) - }; - - clipboard.fire( 'inputTransformation', data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - } ); - } ); + it( 'should process data with microsoft word header', () => { + checkDataProcessing( '', true ); } ); - describe( 'not recognized data', () => { - [ - { - 'text/html': '

Hello world

' - }, - { - 'text/html': '' - } - ].forEach( ( inputData, index ) => { - it( `should not modify data set: #${ index }`, () => { - const data = { - content: htmlDataProcessor.toView( inputData[ 'text/html' ] ), - dataTransfer: createDataTransfer( inputData ) - }; - - clipboard.fire( 'inputTransformation', data ); - - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - } ); - } ); + it( 'should process data with nested microsoft header', () => { + checkDataProcessing( '', true ); } ); - describe( 'already processed data', () => { - [ - { - 'text/html': '

Hello world

' - }, - { - 'text/html': '

Hello world

' - } - ].forEach( ( inputData, index ) => { - it( `should not modify already processed data: #${ index }`, () => { - const data = { - content: htmlDataProcessor.toView( inputData[ 'text/html' ] ), - dataTransfer: createDataTransfer( inputData ), - isTransformedWithPasteFromOffice: true - }; - - const getData = sinon.spy( data.dataTransfer, 'getData' ); - - clipboard.fire( 'inputTransformation', data ); - - // Data object should not be processed - sinon.assert.notCalled( getData ); - } ); - } ); + it( 'should process data from google docs', () => { + checkDataProcessing( '

', true ); + } ); + + it( 'should not process data with regular html', () => { + checkDataProcessing( '

Hello world

', false ); + } ); + + it( 'should not process data with similar headers to MS Word', () => { + checkDataProcessing( '', false ); + } ); + + it( 'should not process again ms word data containing a flag', () => { + checkDataProcessing( '

Hello world

', + false, true ); + } ); + + it( 'should not process again google docs data containing a flag', () => { + checkDataProcessing( '

Hello world

', false, true ); } ); } ); + + // @param {String} inputString html to be processed by paste from office + // @param {Boolean} shouldBeProcessed determines if data should be marked as processed with isTransformedWithPasteFromOffice flag + // @param {Boolean} [isAlreadyProcessed=false] apply flag before paste from office plugin will transform the data object + function checkDataProcessing( inputString, shouldBeProcessed, isAlreadyProcessed = false ) { + // const htmlDataProcessor = new HtmlDataProcessor(); + const data = { + content: htmlDataProcessor.toView( inputString ), + dataTransfer: createDataTransfer( { 'text/html': inputString } ) + }; + const getData = sinon.spy( data.dataTransfer, 'getData' ); + + if ( isAlreadyProcessed ) { + data.isTransformedWithPasteFromOffice = true; + } + + clipboard.fire( 'inputTransformation', data ); + + if ( shouldBeProcessed ) { + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + sinon.assert.called( getData ); + } else if ( isAlreadyProcessed ) { + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + sinon.assert.notCalled( getData ); + } else { + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + sinon.assert.called( getData ); + } + } } ); From a81e493d7ee9208ed28f13a9eda8cd2b85414181 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 16:05:50 +0200 Subject: [PATCH 45/48] remove foreach loops in normalizers check. --- tests/normalizers/googledocsnormalizer.js | 22 +++++---------- tests/normalizers/mswordnormalizer.js | 33 +++++++++++------------ 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/tests/normalizers/googledocsnormalizer.js b/tests/normalizers/googledocsnormalizer.js index ef5d068..d2aa587 100644 --- a/tests/normalizers/googledocsnormalizer.js +++ b/tests/normalizers/googledocsnormalizer.js @@ -10,24 +10,16 @@ describe( 'GoogleDocsNormalizer', () => { const normalizer = new GoogleDocsNormalizer(); describe( 'isActive()', () => { - describe( 'correct data set', () => { - it( 'should be active for google docs data', () => { - const gDocs = '

'; + it( 'should return true from google docs content', () => { + expect( normalizer.isActive( '

' ) ).to.be.true; + } ); - expect( normalizer.isActive( gDocs ) ).to.be.true; - } ); + it( 'should return false for microsoft word content', () => { + expect( normalizer.isActive( '

Foo bar

' ) ).to.be.false; } ); - describe( 'wrong data set', () => { - [ - '

foo

', - '

Foo bar

', - '' - ].forEach( ( htmlString, index ) => { - it( `should be inactive for: #${ index } data set`, () => { - expect( normalizer.isActive( htmlString ) ).to.be.false; - } ); - } ); + it( 'should return false for content form other sources', () => { + expect( normalizer.isActive( '

foo

' ) ).to.be.false; } ); } ); } ); diff --git a/tests/normalizers/mswordnormalizer.js b/tests/normalizers/mswordnormalizer.js index 9bdbef4..b6aad35 100644 --- a/tests/normalizers/mswordnormalizer.js +++ b/tests/normalizers/mswordnormalizer.js @@ -10,26 +10,23 @@ describe( 'MSWordNormalizer', () => { const normalizer = new MSWordNormalizer(); describe( 'isActive()', () => { - describe( 'correct data set', () => { - [ - '

Foo bar

', - '' - ].forEach( ( htmlString, index ) => { - it( `should be active for: #${ index } data set`, () => { - expect( normalizer.isActive( htmlString ) ).to.be.true; - } ); - } ); + it( 'should return true for microsoft word content', () => { + expect( normalizer.isActive( '

Foo bar

' ) ).to.be.true; } ); - describe( 'wrong data set', () => { - [ - '

foo

', - '

' - ].forEach( ( htmlString, index ) => { - it( `should be inactive to for: #${ index } data set`, () => { - expect( normalizer.isActive( htmlString ) ).to.be.false; - } ); - } ); + it( 'should return true for microsoft word content - safari', () => { + expect( normalizer.isActive( '' ) ).to.be.true; + } ); + + it( 'should return false for google docs content', () => { + expect( normalizer.isActive( '

' ) ).to.be.false; + } ); + + it( 'should return false for content fromother sources', () => { + expect( normalizer.isActive( '

foo

' ) ).to.be.false; } ); } ); } ); From a27e2e466262e2c308d4b63774642522df21d4e2 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 16:06:43 +0200 Subject: [PATCH 46/48] Fix code comments. --- tests/normalizers/googledocsnormalizer.js | 2 +- tests/normalizers/mswordnormalizer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/normalizers/googledocsnormalizer.js b/tests/normalizers/googledocsnormalizer.js index d2aa587..58b9309 100644 --- a/tests/normalizers/googledocsnormalizer.js +++ b/tests/normalizers/googledocsnormalizer.js @@ -5,7 +5,7 @@ import GoogleDocsNormalizer from '../../src/normalizers/googledocsnormalizer'; -// exec() of the google docs normalizer is tested with autogenerated normalization tests. +// `execute()` of the google docs normalizer is tested with autogenerated normalization tests. describe( 'GoogleDocsNormalizer', () => { const normalizer = new GoogleDocsNormalizer(); diff --git a/tests/normalizers/mswordnormalizer.js b/tests/normalizers/mswordnormalizer.js index b6aad35..db72e02 100644 --- a/tests/normalizers/mswordnormalizer.js +++ b/tests/normalizers/mswordnormalizer.js @@ -5,7 +5,7 @@ import MSWordNormalizer from '../../src/normalizers/mswordnormalizer'; -// `exec()` of the msword normalizer is tested with autogenerated normalization tests. +// `execute()` of the msword normalizer is tested with autogenerated normalization tests. describe( 'MSWordNormalizer', () => { const normalizer = new MSWordNormalizer(); From d79a22431f07ac7ecf0ec9c916bbd6bb4d86d548 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 16:37:38 +0200 Subject: [PATCH 47/48] Remove leftover, correct namespace. --- src/filters/removeboldwrapper.js | 2 +- tests/pastefromoffice.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/filters/removeboldwrapper.js b/src/filters/removeboldwrapper.js index 9bf8f0f..673c2ea 100644 --- a/src/filters/removeboldwrapper.js +++ b/src/filters/removeboldwrapper.js @@ -4,7 +4,7 @@ */ /** - * @module paste-from-office/filters/removeboldtagwrapper + * @module paste-from-office/filters/removeboldwrapper */ /** diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 8eac576..55602aa 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -75,7 +75,6 @@ describe( 'PasteFromOffice', () => { // @param {Boolean} shouldBeProcessed determines if data should be marked as processed with isTransformedWithPasteFromOffice flag // @param {Boolean} [isAlreadyProcessed=false] apply flag before paste from office plugin will transform the data object function checkDataProcessing( inputString, shouldBeProcessed, isAlreadyProcessed = false ) { - // const htmlDataProcessor = new HtmlDataProcessor(); const data = { content: htmlDataProcessor.toView( inputString ), dataTransfer: createDataTransfer( { 'text/html': inputString } ) From ee7e3a0357eb6736b020739556d3c25320951c19 Mon Sep 17 00:00:00 2001 From: Mateusz Samsel Date: Mon, 29 Jul 2019 16:55:24 +0200 Subject: [PATCH 48/48] Split tests into groups, provide helper for each group. --- tests/pastefromoffice.js | 98 +++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/tests/pastefromoffice.js b/tests/pastefromoffice.js index 55602aa..f69f6b2 100644 --- a/tests/pastefromoffice.js +++ b/tests/pastefromoffice.js @@ -40,62 +40,86 @@ describe( 'PasteFromOffice', () => { } ); describe( 'isTransformedWithPasteFromOffice - flag', () => { - it( 'should process data with microsoft word header', () => { - checkDataProcessing( '', true ); - } ); + describe( 'data which should be marked with flag', () => { + it( 'should process data with microsoft word header', () => { + checkCorrectData( '' ); + } ); - it( 'should process data with nested microsoft header', () => { - checkDataProcessing( '', true ); - } ); + it( 'should process data with nested microsoft header', () => { + checkCorrectData( '' ); + } ); - it( 'should process data from google docs', () => { - checkDataProcessing( '

', true ); - } ); + it( 'should process data from google docs', () => { + checkCorrectData( '

' ); + } ); - it( 'should not process data with regular html', () => { - checkDataProcessing( '

Hello world

', false ); - } ); + function checkCorrectData( inputString ) { + const data = setUpData( inputString ); + const getDataSpy = sinon.spy( data.dataTransfer, 'getData' ); + + clipboard.fire( 'inputTransformation', data ); - it( 'should not process data with similar headers to MS Word', () => { - checkDataProcessing( '', false ); + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + sinon.assert.called( getDataSpy ); + } } ); - it( 'should not process again ms word data containing a flag', () => { - checkDataProcessing( '

Hello world

', - false, true ); + describe( 'data which should not be marked with flag', () => { + it( 'should not process data with regular html', () => { + checkInvalidData( '

Hello world

' ); + } ); + + it( 'should not process data with similar headers to MS Word', () => { + checkInvalidData( '' ); + } ); + + function checkInvalidData( inputString ) { + const data = setUpData( inputString ); + const getDataSpy = sinon.spy( data.dataTransfer, 'getData' ); + + clipboard.fire( 'inputTransformation', data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; + sinon.assert.called( getDataSpy ); + } } ); - it( 'should not process again google docs data containing a flag', () => { - checkDataProcessing( '

Hello world

', false, true ); + describe( 'data which already have the flag', () => { + it( 'should not process again ms word data containing a flag', () => { + checkAlreadyProcessedData( '' + + '

Hello world

' ); + } ); + + it( 'should not process again google docs data containing a flag', () => { + checkAlreadyProcessedData( '

Hello world

' ); + } ); + + function checkAlreadyProcessedData( inputString ) { + const data = setUpData( inputString, true ); + const getDataSpy = sinon.spy( data.dataTransfer, 'getData' ); + + clipboard.fire( 'inputTransformation', data ); + + expect( data.isTransformedWithPasteFromOffice ).to.be.true; + sinon.assert.notCalled( getDataSpy ); + } } ); } ); // @param {String} inputString html to be processed by paste from office - // @param {Boolean} shouldBeProcessed determines if data should be marked as processed with isTransformedWithPasteFromOffice flag - // @param {Boolean} [isAlreadyProcessed=false] apply flag before paste from office plugin will transform the data object - function checkDataProcessing( inputString, shouldBeProcessed, isAlreadyProcessed = false ) { + // @param {Boolean} [isTransformedWithPasteFromOffice=false] if set, marks output data with isTransformedWithPasteFromOffice flag + // @returns {Object} data object simulating content obtained from the clipboard + function setUpData( inputString, isTransformedWithPasteFromOffice = false ) { const data = { content: htmlDataProcessor.toView( inputString ), dataTransfer: createDataTransfer( { 'text/html': inputString } ) }; - const getData = sinon.spy( data.dataTransfer, 'getData' ); - if ( isAlreadyProcessed ) { + if ( isTransformedWithPasteFromOffice ) { data.isTransformedWithPasteFromOffice = true; } - clipboard.fire( 'inputTransformation', data ); - - if ( shouldBeProcessed ) { - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - sinon.assert.called( getData ); - } else if ( isAlreadyProcessed ) { - expect( data.isTransformedWithPasteFromOffice ).to.be.true; - sinon.assert.notCalled( getData ); - } else { - expect( data.isTransformedWithPasteFromOffice ).to.be.undefined; - sinon.assert.called( getData ); - } + return data; } } );