瀏覽代碼

Changed model utilities setData method to use document.enqueueChanges.

Szymon Kupś 9 年之前
父節點
當前提交
369531ab6a

+ 54 - 0
packages/ckeditor5-engine/tests/_utils-tests/model.js

@@ -60,6 +60,7 @@ describe( 'model test utils', () => {
 			const parseSpy = sandbox.spy( setData, '_parse' );
 			const options = {};
 			const data = '<b>btext</b>text';
+			document.schema.registerItem( 'b', '$inline' );
 
 			setData( document, data, options );
 
@@ -73,6 +74,7 @@ describe( 'model test utils', () => {
 			const parseSpy = sandbox.spy( setData, '_parse' );
 			const options = {};
 			const data = '<selection><b>btext</b></selection>';
+			document.schema.registerItem( 'b', '$inline' );
 
 			setData( document, data, options );
 
@@ -82,11 +84,63 @@ describe( 'model test utils', () => {
 			expect( args[ 0 ] ).to.equal( data );
 		} );
 
+		it( 'should insert text', () => {
+			test( 'this is test text', '<selection />this is test text' );
+		} );
+
+		it( 'should insert text with selection around', () => {
+			test( '<selection>this is test text</selection>' );
+		} );
+
+		it( 'should insert text with selection inside #1', () => {
+			test( 'this <selection>is test</selection> text' );
+		} );
+
+		it( 'should insert text with selection inside #2', () => {
+			test( '<selection>this is test</selection> text' );
+		} );
+
+		it( 'should insert text with selection inside #2', () => {
+			test( 'this is <selection>test text</selection>' );
+		} );
+
+		it( 'should insert element', () => {
+			document.schema.registerItem( 'b', '$inline' );
+			test( '<b>foo bar</b>', '<selection /><b>foo bar</b>' );
+		} );
+
+		it( 'should insert element with selection inside #1', () => {
+			document.schema.registerItem( 'b', '$inline' );
+			test( '<b><selection>foo </selection>bar</b>' );
+		} );
+
+		it( 'should insert element with selection inside #2', () => {
+			document.schema.registerItem( 'b', '$inline' );
+			test( '<selection><b>foo </selection>bar</b>' );
+		} );
+
+		it( 'should insert element with selection inside #3', () => {
+			document.schema.registerItem( 'b', '$inline' );
+			test( '<b><selection>foo bar</b></selection>' );
+		} );
+
+		it( 'should insert backward selection', () => {
+			document.schema.registerItem( 'b', '$inline' );
+			test( '<b><selection backward>foo bar</b></selection>' );
+		} );
+
 		it( 'should throw an error when passing invalid document', () => {
 			expect( () => {
 				setData( { invalid: 'document' } );
 			} ).to.throw( TypeError, 'Document needs to be an instance of engine.model.Document.' );
 		} );
+
+		function test( data, expected ) {
+			expected = expected || data;
+
+			setData( document, data );
+			expect( getData( document ) ).to.equal( expected );
+		}
 	} );
 
 	describe( 'stringify', () => {

+ 56 - 22
packages/ckeditor5-engine/tests/_utils/model.js

@@ -43,21 +43,67 @@ getData._stringify = stringify;
 
 /**
  * Sets the contents of the {@link engine.model.Document Document} provided as HTML-like string.
+ * It uses {@link engine.model.Document#enqueueChanges enqueueChanges} method.
+ *
+ * NOTE:
+ * Remember to register elements in {@link engine.model.Document#schema document's schema} before inserting them.
  *
  * @param {engine.model.Document} document
  * @param {String} data HTML-like string to write into Document.
  * @param {Object} options
- * @param {String} [options.rootName] Root name where parsed data will be stored. If not provided, default `main` name will be
- * used.
+ * @param {String} [options.rootName='main'] Root name where parsed data will be stored. If not provided, default `main`
+ * name will be used.
+ * @param {String} [options.batchType='transparent'] Batch type used for inserting elements. See {@link engine.model.Batch#type}.
  */
 export function setData( document, data, options = {} ) {
 	if ( !( document instanceof Document ) ) {
 		throw new TypeError( 'Document needs to be an instance of engine.model.Document.' );
 	}
 
-	setData._parse( data, {
-		document: document,
-		rootName: options.rootName
+	let model, selection;
+	const result = setData._parse( data );
+
+	if ( result.model && result.selection ) {
+		model = result.model;
+		selection = result.selection;
+	} else {
+		model = result;
+	}
+
+	// Save to model.
+	const modelRoot = document.getRoot( options.rootName || 'main' );
+
+	document.enqueueChanges( () => {
+		document.batch( options.batchType || 'transparent' )
+			.remove( Range.createFromElement( modelRoot ) )
+			.insert( Position.createAt( modelRoot, 0 ), model );
+
+		if ( selection ) {
+			const ranges = [];
+
+			for ( let range of selection.getRanges() ) {
+				let start, end;
+
+				// Each range returned from `parse()` method has its root placed in DocumentFragment.
+				// Here we convert each range to have its root re-calculated properly and be placed inside
+				// model document root.
+				if ( range.start.parent instanceof DocumentFragment ) {
+					start = Position.createFromParentAndOffset( modelRoot, range.start.offset );
+				} else {
+					start = Position.createFromParentAndOffset( range.start.parent, range.start.offset );
+				}
+
+				if ( range.end.parent instanceof DocumentFragment ) {
+					end = Position.createFromParentAndOffset( modelRoot, range.end.offset );
+				} else {
+					end = Position.createFromParentAndOffset( range.end.parent, range.end.offset );
+				}
+
+				ranges.push( new Range( start, end ) );
+			}
+
+			document.selection.setRanges( ranges, selection.isBackward );
+		}
 	} );
 }
 
@@ -133,27 +179,15 @@ export function stringify( node, selectionOrPositionOrRange = null ) {
  *
  * @param {String} data HTML-like string to be parsed.
  * @param {Object} options
- * @param {engine.model.Document} [options.document] Document from which root element and selection will be used. If
- * not provided new {engine.model.Document document} instance will be created.
- * @param {String} [options.rootName='main'] When `document` option is provided this root name will be used to create
- * {engine.model.RootElement RootElement} instance.
- * @returns {engine.model.RootElement|Object} Returns parsed RootElement or object with two fields `model`
- * and `selection` when selection ranges were included in data to parse.
+ * @returns {engine.model.Element|engine.model.Text|engine.model.DocumentFragment|Object} Returns parsed model node or
+ * object with two fields `model` and `selection` when selection ranges were included in data to parse.
  */
-export function parse( data, options = {} ) {
+export function parse( data ) {
 	let root, selection;
 	let withSelection = false;
-	const rootName = options.rootName || 'main';
 
-	if ( options.document ) {
-		const document = options.document;
-		root = document.getRoot( rootName );
-		root.removeChildren( 0, root.getChildCount() );
-		selection = document.selection;
-	} else {
-		root = new DocumentFragment();
-		selection = new Selection();
-	}
+	root = new DocumentFragment();
+	selection = new Selection();
 
 	const path = [];
 	let selectionStart, selectionEnd, selectionAttributes, textAttributes;

+ 2 - 0
packages/ckeditor5-engine/tests/conversion/model-selection-to-view-converters.js

@@ -437,6 +437,7 @@ describe( 'table cell selection converter', () => {
 	} );
 
 	it( 'should add a class to the selected table cell', () => {
+		modelDoc.schema.registerItem( 'table', '$block' );
 		test(
 			// table tr#0, table tr#1
 			[ [ 0, 0, 0 ], [ 0, 0, 1 ] ],
@@ -446,6 +447,7 @@ describe( 'table cell selection converter', () => {
 	} );
 
 	it( 'should not be used if selection contains more than just a table cell', () => {
+		modelDoc.schema.registerItem( 'table', '$block' );
 		test(
 			// table tr td#1, table tr#2
 			[ [ 0, 0, 0, 1 ], [ 0, 0, 2 ] ],

+ 6 - 0
packages/ckeditor5-engine/tests/datacontroller.js

@@ -138,6 +138,7 @@ describe( 'DataController', () => {
 
 	describe( 'get', () => {
 		it( 'should get paragraph with text', () => {
+			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			setData( modelDocument, '<paragraph>foo</paragraph>' );
 
 			BuildModelConverterFor( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -146,6 +147,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get empty paragraph', () => {
+			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			setData( modelDocument, '<paragraph></paragraph>' );
 
 			BuildModelConverterFor( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -154,6 +156,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get two paragraphs', () => {
+			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			setData( modelDocument, '<paragraph>foo</paragraph><paragraph>bar</paragraph>' );
 
 			BuildModelConverterFor( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -168,6 +171,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get paragraphs without bold', () => {
+			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			setData( modelDocument, '<paragraph>foo<$text bold=true>bar</$text></paragraph>' );
 
 			BuildModelConverterFor( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -176,6 +180,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get paragraphs with bold', () => {
+			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			setData( modelDocument, '<paragraph>foo<$text bold=true>bar</$text></paragraph>' );
 
 			BuildModelConverterFor( data.modelToView ).fromElement( 'paragraph' ).toElement( 'p' );
@@ -185,6 +190,7 @@ describe( 'DataController', () => {
 		} );
 
 		it( 'should get root name as a parameter', () => {
+			modelDocument.schema.registerItem( 'paragraph', '$block' );
 			setData( modelDocument, '<paragraph>foo</paragraph>', { rootName: 'main' } );
 			setData( modelDocument, 'Bar', { rootName: 'title' } );
 

+ 1 - 0
packages/ckeditor5-engine/tests/model/composer/composer.js

@@ -16,6 +16,7 @@ describe( 'Composer', () => {
 
 	beforeEach( () => {
 		document = new Document();
+		document.schema.registerItem( 'p', '$block' );
 		document.createRoot();
 
 		composer = new Composer();

+ 1 - 0
packages/ckeditor5-engine/tests/model/composer/modifyselection.js

@@ -16,6 +16,7 @@ describe( 'Delete utils', () => {
 
 	beforeEach( () => {
 		document = new Document();
+		document.schema.registerItem( 'p', '$block' );
 		document.createRoot();
 	} );