Explorar o código

Merge branch 't/44b' into t/44

# Conflicts:
#	src/mentionui.js
#	tests/mentionui.js
Maciej Gołaszewski %!s(int64=6) %!d(string=hai) anos
pai
achega
ef102e2e5e

+ 35 - 0
packages/ckeditor5-mention/src/featuredetection.js

@@ -0,0 +1,35 @@
+/**
+ * @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 mention/featuredetection
+ */
+
+/**
+ * Holds feature detection resolutions used by the mention plugin.
+ *
+ * @protected
+ * @namespace
+ */
+export default {
+	/**
+	 * Indicates whether the current browser supports ES2018 Unicode punctuation groups `\p{P}`.
+	 *
+	 * @type {Boolean}
+	 */
+	isPunctuationGroupSupported: ( function() {
+		let punctuationSupported = false;
+		// Feature detection for Unicode punctuation groups. It's added in ES2018. Currently Firefox and Edge does not support it.
+		// See https://github.com/ckeditor/ckeditor5-mention/issues/44#issuecomment-487002174.
+
+		try {
+			punctuationSupported = '.'.search( new RegExp( '[\\p{P}]', 'u' ) ) === 0;
+		} catch ( error ) {
+			// Firefox throws a SyntaxError when the group is unsupported.
+		}
+
+		return punctuationSupported;
+	}() )
+};

+ 6 - 14
packages/ckeditor5-mention/src/mentionui.js

@@ -12,9 +12,9 @@ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler';
 import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
+import featureDetection from './featuredetection';
 import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import env from '@ckeditor/ckeditor5-utils/src/env';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 
 import TextWatcher from './textwatcher';
@@ -536,24 +536,16 @@ function getBalloonPanelPositions( preferredPosition ) {
 
 // Creates a RegExp pattern for the marker.
 //
+// Function has to be exported to achieve 100% code coverage.
+//
 // @param {String} marker
 // @param {Number} minimumCharacters
 // @returns {RegExp}
-function createRegExp( marker, minimumCharacters ) {
+export function createRegExp( marker, minimumCharacters ) {
 	const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`;
+	const patternBase = featureDetection.isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\'';
 
-	if ( !env.isEdge ) {
-		// Unfortunately Edge does not throw on `/[\p{Ps}\p{Pi}]/u` as it does on `/\p{Ps}\p{Pi}/u (no square brackets in latter).
-		try {
-			// Uses the ES2018 syntax. See ckeditor5-mention#44.
-			return new RegExp( buildPattern( '\\p{Ps}\\p{Pi}"\'', marker, numberOfCharacters ), 'u' );
-		} catch ( error ) {
-			// It's OK we're fallback to non ES2018 RegExp later.
-		}
-	}
-
-	// ES2018 RegExp Unicode property escapes are not supported - fallback to save character list.
-	return new RegExp( buildPattern( '\\(\\[{"\'', marker, numberOfCharacters ), 'u' );
+	return new RegExp( buildPattern( patternBase, marker, numberOfCharacters ), 'u' );
 }
 
 // Helper to build a RegExp pattern string for the marker.

+ 26 - 93
packages/ckeditor5-mention/tests/mentionui.js

@@ -3,7 +3,7 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
-/* global document, setTimeout, Event, window */
+/* global window, document, setTimeout, Event */
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
@@ -18,7 +18,8 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon';
 import env from '@ckeditor/ckeditor5-utils/src/env';
 
-import MentionUI from '../src/mentionui';
+import MentionUI, { createRegExp } from '../src/mentionui';
+import featureDetection from '../src/featuredetection';
 import MentionEditing from '../src/mentionediting';
 import MentionsView from '../src/ui/mentionsview';
 
@@ -276,8 +277,6 @@ describe( 'MentionUI', () => {
 	} );
 
 	describe( 'typing integration', () => {
-		const supportsES2018Escapes = checkES2018RegExpSupport();
-
 		it( 'should show panel for matched marker after typing minimum characters', () => {
 			return createClassicTestEditor( { feeds: [ Object.assign( { minimumCharacters: 2 }, staticConfig.feeds[ 0 ] ) ] } )
 				.then( () => {
@@ -443,94 +442,40 @@ describe( 'MentionUI', () => {
 		} );
 
 		describe( 'ES2018 RegExp Unicode property escapes fallback', () => {
-			// Cache the original RegExp to restore it after the tests.
-			const RegExp = window.RegExp;
-
-			beforeEach( () => {
-				// The FakeRegExp throws on first call - it simulates syntax error of ES2018 syntax usage
-				// on browsers other then Chrome. See ckeditor5-mention#44.
-				function FakeRegExp( pattern, flags ) {
-					if ( pattern.includes( '\\p{Ps}' ) ) {
-						throw new SyntaxError( 'invalid identity escape in regular expression' );
-					}
-
-					return new RegExp( pattern, flags );
-				}
+			let regExpStub;
 
-				window.RegExp = FakeRegExp;
+			// Cache the original value to restore it after the tests.
+			const originalPunctuationSupport = featureDetection.isPunctuationGroupSupported;
 
-				return createClassicTestEditor( staticConfig );
-			} );
-
-			afterEach( () => {
-				// Restore the original RegExp.
-				window.RegExp = RegExp;
+			before( () => {
+				featureDetection.isPunctuationGroupSupported = false;
 			} );
 
-			it( 'should fallback to old method if browser does not support unicode property escapes', () => {
-				setData( model, '<paragraph>[] foo</paragraph>' );
-
-				model.change( writer => {
-					writer.insertText( '〈', doc.selection.getFirstPosition() );
-				} );
-
-				model.change( writer => {
-					writer.insertText( '@', doc.selection.getFirstPosition() );
-				} );
+			beforeEach( () => {
+				return createClassicTestEditor( staticConfig )
+					.then( editor => {
+						regExpStub = sinon.stub( window, 'RegExp' );
 
-				return waitForDebounce()
-					.then( () => {
-						expect( panelView.isVisible ).to.be.false;
-						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
+						return editor;
 					} );
 			} );
 
-			it( 'should fallback to old method if browser does not support unicode property escapes (on Edge)', () => {
-				// Stub the isEdge for coverage tests in other browsers.
-				testUtils.sinon.stub( env, 'isEdge' ).get( () => true );
-
-				setData( model, '<paragraph>[] foo</paragraph>' );
-
-				model.change( writer => {
-					writer.insertText( '〈', doc.selection.getFirstPosition() );
-				} );
-
-				model.change( writer => {
-					writer.insertText( '@', doc.selection.getFirstPosition() );
-				} );
-
-				return waitForDebounce()
-					.then( () => {
-						expect( panelView.isVisible ).to.be.false;
-						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
-					} );
+			after( () => {
+				featureDetection.isPunctuationGroupSupported = originalPunctuationSupport;
 			} );
-		} );
 
-		describe( 'ES2018 RegExp Unicode property escapes fallback on Edge', () => {
-			beforeEach( () => {
-				// Most tests assume non-edge environment but we do not set `contenteditable=false` on Edge so stub `env.isEdge`.
-				testUtils.sinon.stub( env, 'isEdge' ).get( () => true );
-
-				return createClassicTestEditor( staticConfig );
+			it( 'returns a simplified RegExp for browsers not supporting Unicode punctuation groups', () => {
+				featureDetection.isPunctuationGroupSupported = false;
+				createRegExp( '@', 2 );
+				sinon.assert.calledOnce( regExpStub );
+				sinon.assert.calledWithExactly( regExpStub, '(^|[ \\(\\[{"\'])([@])([_a-zA-Z0-9À-ž]{2,}?)$', 'u' );
 			} );
 
-			it( 'should fallback to old method if browser does not support unicode property escapes (on Edge)', () => {
-				setData( model, '<paragraph>[] foo</paragraph>' );
-
-				model.change( writer => {
-					writer.insertText( '〈', doc.selection.getFirstPosition() );
-				} );
-
-				model.change( writer => {
-					writer.insertText( '@', doc.selection.getFirstPosition() );
-				} );
-
-				return waitForDebounce()
-					.then( () => {
-						expect( panelView.isVisible ).to.be.false;
-						expect( editor.model.markers.has( 'mention' ) ).to.be.false;
-					} );
+			it( 'returns a ES2018 RegExp for browsers supporting Unicode punctuation groups', () => {
+				featureDetection.isPunctuationGroupSupported = true;
+				createRegExp( '@', 2 );
+				sinon.assert.calledOnce( regExpStub );
+				sinon.assert.calledWithExactly( regExpStub, '(^|[ \\p{Ps}\\p{Pi}"\'])([@])([_a-zA-Z0-9À-ž]{2,}?)$', 'u' );
 			} );
 		} );
 
@@ -615,7 +560,7 @@ describe( 'MentionUI', () => {
 				// Belongs to Pi (Punctuation, Initial quote) group:
 				'«', '‹', '⸌', ' ⸂', '⸠'
 			] ) {
-				testOpeningPunctuationCharacter( character, !supportsES2018Escapes );
+				testOpeningPunctuationCharacter( character, !featureDetection.isPunctuationGroupSupported );
 			}
 
 			it( 'should not show panel for marker in the middle of other word', () => {
@@ -1783,16 +1728,4 @@ describe( 'MentionUI', () => {
 			expect( mentionForCommand[ key ] ).to.equal( item[ key ] );
 		}
 	}
-
-	function checkES2018RegExpSupport() {
-		let supportsES2018Escapes = false;
-
-		try {
-			supportsES2018Escapes = new RegExp( '\\p{Ps}', 'u' ).test( '〈' );
-		} catch ( error ) {
-			// It's Ok we skip test on this browser.
-		}
-
-		return supportsES2018Escapes;
-	}
 } );