Răsfoiți Sursa

Merge branch 'master' into t/ckeditor5/416d

Piotrek Koszuliński 6 ani în urmă
părinte
comite
cd5323703c

+ 3 - 3
packages/ckeditor5-utils/.travis.yml

@@ -1,9 +1,11 @@
 sudo: required
-dist: trusty
+dist: xenial
 addons:
   chrome: stable
   firefox: latest
 language: node_js
+services:
+- xvfb
 node_js:
 - '8'
 cache:
@@ -13,8 +15,6 @@ branches:
   - stable
 before_install:
 - export START_TIME=$( date +%s )
-- export DISPLAY=:99.0
-- sh -e /etc/init.d/xvfb start
 - npm i -g yarn
 install:
 - yarn add @ckeditor/ckeditor5-dev-tests

+ 0 - 1
packages/ckeditor5-utils/README.md

@@ -1,7 +1,6 @@
 CKEditor 5 utilities
 ========================================
 
-[![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-utils.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-utils)
 [![Build Status](https://travis-ci.org/ckeditor/ckeditor5-utils.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-utils)
 [![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-utils/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-utils?branch=master)

+ 2 - 0
packages/ckeditor5-utils/package.json

@@ -17,9 +17,11 @@
     "@ckeditor/ckeditor5-editor-classic": "^12.1.4",
     "@ckeditor/ckeditor5-core": "^12.3.0",
     "@ckeditor/ckeditor5-engine": "^14.0.0",
+    "assertion-error": "^1.1.0",
     "eslint": "^5.5.0",
     "eslint-config-ckeditor5": "^2.0.0",
     "husky": "^1.3.1",
+    "js-beautify": "^1.10.2",
     "lint-staged": "^7.0.0"
   },
   "engines": {

+ 17 - 6
packages/ckeditor5-utils/src/dom/rect.js

@@ -310,23 +310,34 @@ export default class Rect {
 	 */
 	excludeScrollbarsAndBorders() {
 		const source = this._source;
-		let scrollBarWidth, scrollBarHeight;
+		let scrollBarWidth, scrollBarHeight, direction;
 
 		if ( isWindow( source ) ) {
 			scrollBarWidth = source.innerWidth - source.document.documentElement.clientWidth;
 			scrollBarHeight = source.innerHeight - source.document.documentElement.clientHeight;
+			direction = source.getComputedStyle( source.document.documentElement ).direction;
 		} else {
 			const borderWidths = getBorderWidths( this._source );
 
-			scrollBarWidth = source.offsetWidth - source.clientWidth;
-			scrollBarHeight = source.offsetHeight - source.clientHeight;
+			scrollBarWidth = source.offsetWidth - source.clientWidth - borderWidths.left - borderWidths.right;
+			scrollBarHeight = source.offsetHeight - source.clientHeight - borderWidths.top - borderWidths.bottom;
+			direction = source.ownerDocument.defaultView.getComputedStyle( source ).direction;
 
-			this.moveBy( borderWidths.left, borderWidths.top );
+			this.left += borderWidths.left;
+			this.top += borderWidths.top;
+			this.right -= borderWidths.right;
+			this.bottom -= borderWidths.bottom;
+			this.width = this.right - this.left;
+			this.height = this.bottom - this.top;
 		}
 
-		// Assuming LTR scrollbars. TODO: RTL.
 		this.width -= scrollBarWidth;
-		this.right -= scrollBarWidth;
+
+		if ( direction === 'ltr' ) {
+			this.right -= scrollBarWidth;
+		} else {
+			this.left += scrollBarWidth;
+		}
 
 		this.height -= scrollBarHeight;
 		this.bottom -= scrollBarHeight;

+ 102 - 1
packages/ckeditor5-utils/tests/_utils-tests/utils.js

@@ -3,10 +3,11 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
+import AssertionError from 'assertion-error';
 import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import ObservableMixin from '../../src/observablemixin';
 import EmitterMixin from '../../src/emittermixin';
-import { createObserver } from '../_utils/utils';
+import { assertEqualMarkup, createObserver } from '../_utils/utils';
 
 describe( 'utils - testUtils', () => {
 	afterEach( () => {
@@ -81,4 +82,104 @@ describe( 'utils - testUtils', () => {
 			} );
 		} );
 	} );
+
+	describe( 'assertEqualMarkup()', () => {
+		it( 'should not throw for equal strings', () => {
+			expect( assertEqualMarkup( 'foo', 'foo' ) ).to.not.throw;
+		} );
+
+		it( 'should throw AssertionError for not equal strings', () => {
+			try {
+				assertEqualMarkup( 'foo', 'bar' );
+			} catch ( assertionError ) {
+				expect( assertionError ).to.be.instanceOf( AssertionError );
+			}
+		} );
+
+		it( 'should throw with default (short) message', () => {
+			try {
+				assertEqualMarkup( 'foo', 'bar' );
+			} catch ( assertionError ) {
+				expect( assertionError.message ).to.equal( 'Expected markup strings to be equal' );
+			}
+		} );
+
+		it( 'should throw with passed message', () => {
+			try {
+				assertEqualMarkup( 'foo', 'bar', 'baz' );
+			} catch ( assertionError ) {
+				expect( assertionError.message ).to.equal( 'baz' );
+			}
+		} );
+
+		it( 'should format actual string', () => {
+			try {
+				assertEqualMarkup( '<div><p><span>foo</span></p></div>', 'bar' );
+			} catch ( assertionError ) {
+				expect( assertionError.actual ).to.equal(
+					'<div>\n' +
+					'  <p><span>foo</span></p>\n' +
+					'</div>'
+				);
+			}
+		} );
+
+		it( 'should format expected string', () => {
+			try {
+				assertEqualMarkup( 'foo', '<div><p><span>foo</span></p></div>' );
+			} catch ( assertionError ) {
+				expect( assertionError.expected ).to.equal(
+					'<div>\n' +
+					'  <p><span>foo</span></p>\n' +
+					'</div>'
+				);
+			}
+		} );
+
+		it( 'should format model text node with attributes as inline', () => {
+			try {
+				assertEqualMarkup( 'foo', '<paragraph><$text bold="true">foo</$text></paragraph>' );
+			} catch ( assertionError ) {
+				expect( assertionError.expected ).to.equal(
+					'<paragraph><$text bold="true">foo</$text></paragraph>'
+				);
+			}
+		} );
+
+		it( 'should format nested model structure properly', () => {
+			try {
+				assertEqualMarkup( 'foo',
+					'<blockQuote>' +
+						'<table>' +
+							'<tableRow>' +
+								'<tableCell>' +
+									'<paragraph><$text bold="true">foo</$text></paragraph>' +
+								'</tableCell>' +
+								'<tableCell>' +
+									'<paragraph><$text bold="true">bar</$text></paragraph>' +
+									'<paragraph><$text bold="true">baz</$text></paragraph>' +
+								'</tableCell>' +
+							'</tableRow>' +
+						'</table>' +
+					'</blockQuote>'
+				);
+			} catch ( assertionError ) {
+				expect( assertionError.expected ).to.equal(
+					'<blockQuote>\n' +
+					'  <table>\n' +
+					'    <tableRow>\n' +
+					'      <tableCell>\n' +
+					'        <paragraph><$text bold="true">foo</$text></paragraph>\n' +
+					'      </tableCell>\n' +
+					'      <tableCell>\n' +
+					'        <paragraph><$text bold="true">bar</$text></paragraph>\n' +
+					'        <paragraph><$text bold="true">baz</$text></paragraph>\n' +
+					'      </tableCell>\n' +
+					'    </tableRow>\n' +
+					'  </table>\n' +
+					'</blockQuote>'
+				);
+			}
+		} );
+	} );
 } );

+ 44 - 0
packages/ckeditor5-utils/tests/_utils/utils.js

@@ -5,6 +5,9 @@
 
 /* global console:false */
 
+import AssertionError from 'assertion-error';
+import { html_beautify as beautify } from 'js-beautify/js/lib/beautify-html';
+
 import EmitterMixin from '../../src/emittermixin';
 import CKEditorError from '../../src/ckeditorerror';
 import areConnectedThroughProperties from '../../src/areconnectedthroughproperties';
@@ -141,3 +144,44 @@ export function assertCKEditorError( err, message, editorThatShouldBeFindableFro
 		expect( err.data ).to.deep.equal( data );
 	}
 }
+
+/**
+ * An assertion util test whether two given strings containing markup language are equal.
+ * Unlike `expect().to.equal()` form Chai assertion library, this util formats the markup before showing a diff.
+ *
+ * This util can be used to test HTML strings and string containing serialized model.
+ *
+ *		// Will throw an error that is handled as assertion error by Chai.
+ *		assertEqualMarkup(
+ *			'<paragraph><$text foo="bar">baz</$text></paragraph>',
+ *			'<paragraph><$text foo="bar">baaz</$text></paragraph>',
+ *		);
+ *
+ * @param {String} actual An actual string.
+ * @param {String} expected An expected string.
+ * @param {String} [message="Expected two markup strings to be equal"] Optional error message.
+ */
+export function assertEqualMarkup( actual, expected, message = 'Expected markup strings to be equal' ) {
+	if ( actual != expected ) {
+		throw new AssertionError( message, {
+			actual: formatMarkup( actual ),
+			expected: formatMarkup( expected ),
+			showDiff: true
+		} );
+	}
+}
+
+// Renames the $text occurrences as it is not properly formatted by the beautifier - it is tread as a block.
+const TEXT_TAG_PLACEHOLDER = 'span data-cke="true"';
+const TEXT_TAG_PLACEHOLDER_REGEXP = new RegExp( TEXT_TAG_PLACEHOLDER, 'g' );
+
+function formatMarkup( string ) {
+	const htmlSafeString = string.replace( /\$text/g, TEXT_TAG_PLACEHOLDER );
+
+	const beautifiedMarkup = beautify( htmlSafeString, {
+		indent_size: 2,
+		space_in_empty_paren: true
+	} );
+
+	return beautifiedMarkup.replace( TEXT_TAG_PLACEHOLDER_REGEXP, '$text' );
+}

+ 104 - 15
packages/ckeditor5-utils/tests/dom/rect.js

@@ -792,18 +792,21 @@ describe( 'Rect', () => {
 	} );
 
 	describe( 'excludeScrollbarsAndBorders()', () => {
-		it( 'should exclude scrollbars and borders of a HTMLElement', () => {
+		it( 'should exclude scrollbars and borders of a HTMLElement (dir="ltr")', () => {
 			const element = document.createElement( 'div' );
 
 			sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
-			sinon.stub( window, 'getComputedStyle' ).returns( {
-				borderTopWidth: '5px',
-				borderRightWidth: '10px',
-				borderLeftWidth: '5px',
-				borderBottomWidth: '10px'
-			} );
+			sinon.stub( window, 'getComputedStyle' )
+				.withArgs( element )
+				.returns( {
+					borderTopWidth: '5px',
+					borderRightWidth: '10px',
+					borderLeftWidth: '5px',
+					borderBottomWidth: '10px',
+					direction: 'ltr'
+				} );
 
-			// Simulate 5px srollbars.
+			// Simulate 5px scrollbars.
 			Object.defineProperties( element, {
 				offsetWidth: {
 					value: 20
@@ -812,24 +815,65 @@ describe( 'Rect', () => {
 					value: 20
 				},
 				clientWidth: {
-					value: 10
+					value: 0
 				},
 				clientHeight: {
-					value: 10
+					value: 0
 				}
 			} );
 
 			assertRect( new Rect( element ).excludeScrollbarsAndBorders(), {
 				top: 15,
-				right: 35,
-				bottom: 25,
+				right: 25,
+				bottom: 15,
 				left: 25,
-				width: 10,
-				height: 10
+				width: 0,
+				height: 0
+			} );
+		} );
+
+		it( 'should exclude scrollbars and borders of a HTMLElement (dir="rtl")', () => {
+			const element = document.createElement( 'div' );
+
+			element.setAttribute( 'dir', 'rtl' );
+			sinon.stub( element, 'getBoundingClientRect' ).returns( geometry );
+			sinon.stub( window, 'getComputedStyle' )
+				.withArgs( element )
+				.returns( {
+					borderTopWidth: '5px',
+					borderRightWidth: '10px',
+					borderLeftWidth: '5px',
+					borderBottomWidth: '10px',
+					direction: 'rtl'
+				} );
+
+			// Simulate 5px scrollbars.
+			Object.defineProperties( element, {
+				offsetWidth: {
+					value: 20
+				},
+				offsetHeight: {
+					value: 20
+				},
+				clientWidth: {
+					value: 0
+				},
+				clientHeight: {
+					value: 0
+				}
+			} );
+
+			assertRect( new Rect( element ).excludeScrollbarsAndBorders(), {
+				top: 15,
+				right: 30,
+				bottom: 15,
+				left: 30,
+				width: 0,
+				height: 0
 			} );
 		} );
 
-		it( 'should exclude scrollbars from viewport\'s rect', () => {
+		it( 'should exclude scrollbars from viewport\'s rect (dir="ltr")', () => {
 			sinon.stub( window, 'innerWidth' ).value( 1000 );
 			sinon.stub( window, 'innerHeight' ).value( 500 );
 			sinon.stub( window, 'scrollX' ).value( 100 );
@@ -840,6 +884,12 @@ describe( 'Rect', () => {
 				clientHeight: 490
 			} );
 
+			sinon.stub( window, 'getComputedStyle' )
+				.withArgs( document.documentElement )
+				.returns( {
+					direction: 'ltr'
+				} );
+
 			assertRect( new Rect( window ).excludeScrollbarsAndBorders(), {
 				top: 0,
 				right: 990,
@@ -850,6 +900,33 @@ describe( 'Rect', () => {
 			} );
 		} );
 
+		it( 'should exclude scrollbars from viewport\'s rect (dir="rtl")', () => {
+			sinon.stub( window, 'innerWidth' ).value( 1000 );
+			sinon.stub( window, 'innerHeight' ).value( 500 );
+			sinon.stub( window, 'scrollX' ).value( 100 );
+			sinon.stub( window, 'scrollY' ).value( 200 );
+
+			sinon.stub( document, 'documentElement' ).value( {
+				clientWidth: 990,
+				clientHeight: 490
+			} );
+
+			sinon.stub( window, 'getComputedStyle' )
+				.withArgs( document.documentElement )
+				.returns( {
+					direction: 'rtl'
+				} );
+
+			assertRect( new Rect( window ).excludeScrollbarsAndBorders(), {
+				top: 0,
+				right: 1000,
+				bottom: 490,
+				left: 10,
+				width: 990,
+				height: 490
+			} );
+		} );
+
 		it( 'should work for a window in an iframe', done => {
 			const iframe = document.createElement( 'iframe' );
 
@@ -864,6 +941,12 @@ describe( 'Rect', () => {
 				clientHeight: 490
 			} );
 
+			sinon.stub( window, 'getComputedStyle' )
+				.withArgs( document.documentElement )
+				.returns( {
+					direction: 'ltr'
+				} );
+
 			iframe.addEventListener( 'load', () => {
 				const iframeWindow = iframe.contentWindow;
 
@@ -877,6 +960,12 @@ describe( 'Rect', () => {
 					clientHeight: 230
 				} );
 
+				sinon.stub( iframeWindow, 'getComputedStyle' )
+					.withArgs( iframeWindow.document.documentElement )
+					.returns( {
+						direction: 'ltr'
+					} );
+
 				assertRect( new Rect( iframeWindow ).excludeScrollbarsAndBorders(), {
 					top: 0,
 					right: 480,

+ 6 - 3
packages/ckeditor5-utils/tests/dom/scroll.js

@@ -28,7 +28,8 @@ describe( 'scrollAncestorsToShowTarget()', () => {
 			borderTopWidth: '0px',
 			borderRightWidth: '0px',
 			borderBottomWidth: '0px',
-			borderLeftWidth: '0px'
+			borderLeftWidth: '0px',
+			direction: 'ltr'
 		} );
 
 		stubRect( firstAncestor, {
@@ -169,7 +170,8 @@ describe( 'scrollViewportToShowTarget()', () => {
 			borderTopWidth: '0px',
 			borderRightWidth: '0px',
 			borderBottomWidth: '0px',
-			borderLeftWidth: '0px'
+			borderLeftWidth: '0px',
+			direction: 'ltr'
 		} );
 
 		// Assuming 20px v- and h-scrollbars here.
@@ -228,7 +230,8 @@ describe( 'scrollViewportToShowTarget()', () => {
 					borderTopWidth: '0px',
 					borderRightWidth: '0px',
 					borderBottomWidth: '0px',
-					borderLeftWidth: '0px'
+					borderLeftWidth: '0px',
+					direction: 'ltr'
 				} );
 
 				// Assuming 20px v- and h-scrollbars here.

+ 8 - 4
packages/ckeditor5-utils/tests/locale.js

@@ -6,17 +6,18 @@
 /* globals console */
 
 import Locale from '../src/locale';
-import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 
 describe( 'Locale', () => {
 	let locale;
 
-	testUtils.createSinonSandbox();
-
 	beforeEach( () => {
 		locale = new Locale();
 	} );
 
+	afterEach( () => {
+		sinon.restore();
+	} );
+
 	describe( 'constructor', () => {
 		it( 'sets the #language', () => {
 			const locale = new Locale( {
@@ -149,11 +150,14 @@ describe( 'Locale', () => {
 
 	describe( 'language()', () => {
 		it( 'should return #uiLanguage', () => {
+			const stub = sinon.stub( console, 'warn' );
+
 			expect( locale.language ).to.equal( locale.uiLanguage );
+			sinon.assert.calledWithMatch( stub, 'locale-deprecated-language-property' );
 		} );
 
 		it( 'should warn about deprecation', () => {
-			const stub = testUtils.sinon.stub( console, 'warn' );
+			const stub = sinon.stub( console, 'warn' );
 
 			expect( locale.language ).to.equal( 'en' );
 			sinon.assert.calledWithMatch( stub, 'locale-deprecated-language-property' );