From b9ebfa90d01250bb8262fcc526d0baaca5bc49a0 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 10 May 2019 12:26:19 +0200 Subject: [PATCH 1/4] Internal: Remove browser sniffing for edge and instead use feature detection for both Firefox and Edge. --- src/mentionui.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/mentionui.js b/src/mentionui.js index 84a1ce9..9697261 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -14,7 +14,6 @@ import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsid import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; 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'; @@ -534,6 +533,20 @@ function getBalloonPanelPositions( preferredPosition ) { ]; } +const 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 ) { + // It's OK we're fallback to non ES2018 RegExp later. + } + + return punctuationSupported; +}() ); + // Creates a regex for marker. // // @param {String} marker @@ -541,19 +554,9 @@ function getBalloonPanelPositions( preferredPosition ) { // @returns {String} function createRegExp( marker, minimumCharacters ) { const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`; + const patternBase = isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; - if ( !env.isEdge ) { - // Unfortunately Edge does not throw on `/[\p{Ps}]/u` as it does on `/\p{Ps}/u (no square brackets in latter). - try { - // Uses the ES2018 syntax. See ckeditor5-mention#44. - return new RegExp( buildPattern( '\\p{Ps}"\'', 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' ); } // Creates a regex pattern for the marker. From a17d84f129124d72901480f977a4c3507cde2aa4 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 10 May 2019 15:11:03 +0200 Subject: [PATCH 2/4] Internal: Restructured the code to achieve 100% code coverage. --- src/featuredetection.js | 35 +++++++++++++++++++ src/mentionui.js | 17 ++-------- tests/mentionui.js | 75 ++++++----------------------------------- 3 files changed, 47 insertions(+), 80 deletions(-) create mode 100644 src/featuredetection.js diff --git a/src/featuredetection.js b/src/featuredetection.js new file mode 100644 index 0000000..234b046 --- /dev/null +++ b/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; + }() ) +}; diff --git a/src/mentionui.js b/src/mentionui.js index 9697261..e4f5454 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -12,6 +12,7 @@ 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 ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; @@ -533,20 +534,6 @@ function getBalloonPanelPositions( preferredPosition ) { ]; } -const 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 ) { - // It's OK we're fallback to non ES2018 RegExp later. - } - - return punctuationSupported; -}() ); - // Creates a regex for marker. // // @param {String} marker @@ -554,7 +541,7 @@ const isPunctuationGroupSupported = ( function() { // @returns {String} function createRegExp( marker, minimumCharacters ) { const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`; - const patternBase = isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; + const patternBase = featureDetection.isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; return new RegExp( buildPattern( patternBase, marker, numberOfCharacters ), 'u' ); } diff --git a/tests/mentionui.js b/tests/mentionui.js index e94e38d..a1e15f1 100644 --- a/tests/mentionui.js +++ b/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 document, setTimeout, Event */ import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; @@ -19,6 +19,7 @@ import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextu import env from '@ckeditor/ckeditor5-utils/src/env'; import MentionUI from '../src/mentionui'; +import featureDetection from '../src/featuredetection'; import MentionEditing from '../src/mentionediting'; import MentionsView from '../src/ui/mentionsview'; @@ -444,78 +445,22 @@ describe( 'MentionUI', () => { describe( 'ES2018 RegExp Unicode property escapes fallback', () => { // Cache the original RegExp to restore it after the tests. - const RegExp = window.RegExp; + const originalPunctationSupport = featureDetection.isPunctuationGroupSupported; - 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 ); - } - - window.RegExp = FakeRegExp; + before( () => { + featureDetection.isPunctuationGroupSupported = false; + } ); + beforeEach( () => { return createClassicTestEditor( staticConfig ); } ); - afterEach( () => { + after( () => { // Restore the original RegExp. - window.RegExp = RegExp; - } ); - - it( 'should fallback to old method if browser does not support unicode property escapes', () => { - setData( model, '[] foo' ); - - 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( 'should fallback to old method if browser does not support unicode property escapes (on Edge)', () => { - // Stub the isEdge for covarage tests in other browsers. - testUtils.sinon.stub( env, 'isEdge' ).get( () => true ); - - setData( model, '[] foo' ); - - 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; - } ); - } ); - } ); - - 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 ); + featureDetection.isPunctuationGroupSupported = originalPunctationSupport; } ); - it( 'should fallback to old method if browser does not support unicode property escapes (on Edge)', () => { + it( 'should fallback to simpler RegExp if browser does not support unicode property escapes', () => { setData( model, '[] foo' ); model.change( writer => { From fcaeb310f71fb4c1d913334c4024a196f93b69f1 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 10 May 2019 16:12:02 +0200 Subject: [PATCH 3/4] Internal: Final adjustments to achieve 100% code coverage. --- src/mentionui.js | 4 +++- tests/mentionui.js | 46 ++++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/mentionui.js b/src/mentionui.js index e4f5454..5f9b6c3 100644 --- a/src/mentionui.js +++ b/src/mentionui.js @@ -536,10 +536,12 @@ function getBalloonPanelPositions( preferredPosition ) { // Creates a regex for marker. // +// Function has to be exported to achieve 100% code coverage. +// // @param {String} marker // @param {Number} minimumCharacters // @returns {String} -function createRegExp( marker, minimumCharacters ) { +export function createRegExp( marker, minimumCharacters ) { const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`; const patternBase = featureDetection.isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; diff --git a/tests/mentionui.js b/tests/mentionui.js index a1e15f1..9769856 100644 --- a/tests/mentionui.js +++ b/tests/mentionui.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ -/* global document, setTimeout, Event */ +/* 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,7 @@ 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'; @@ -444,38 +444,40 @@ describe( 'MentionUI', () => { } ); describe( 'ES2018 RegExp Unicode property escapes fallback', () => { - // Cache the original RegExp to restore it after the tests. - const originalPunctationSupport = featureDetection.isPunctuationGroupSupported; + let regExpStub; + + // Cache the original value to restore it after the tests. + const originalPunctuationSupport = featureDetection.isPunctuationGroupSupported; before( () => { featureDetection.isPunctuationGroupSupported = false; } ); beforeEach( () => { - return createClassicTestEditor( staticConfig ); + return createClassicTestEditor( staticConfig ) + .then( editor => { + regExpStub = sinon.stub( window, 'RegExp' ); + + return editor; + } ); } ); after( () => { - // Restore the original RegExp. - featureDetection.isPunctuationGroupSupported = originalPunctationSupport; + featureDetection.isPunctuationGroupSupported = originalPunctuationSupport; } ); - it( 'should fallback to simpler RegExp if browser does not support unicode property escapes', () => { - setData( model, '[] foo' ); - - model.change( writer => { - writer.insertText( '〈', doc.selection.getFirstPosition() ); - } ); - - model.change( writer => { - writer.insertText( '@', doc.selection.getFirstPosition() ); - } ); + 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' ); + } ); - 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' ); } ); } ); From 7d2f6c4768880297cbabd24eb15db630f0283ce7 Mon Sep 17 00:00:00 2001 From: Marek Lewandowski Date: Fri, 10 May 2019 16:25:44 +0200 Subject: [PATCH 4/4] Tests: Reused common code for RegExp punctuation groups feature detection. --- tests/mentionui.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/mentionui.js b/tests/mentionui.js index 9769856..d81f8b2 100644 --- a/tests/mentionui.js +++ b/tests/mentionui.js @@ -277,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( () => { @@ -557,7 +555,7 @@ describe( 'MentionUI', () => { // Excerpt of opening parenthesis type characters that tests ES2018 Unicode property escapes on supported environment. for ( const character of [ '〈', '„', '﹛', '⦅', '{' ] ) { - testOpeningPunctuationCharacter( character, !supportsES2018Escapes ); + testOpeningPunctuationCharacter( character, !featureDetection.isPunctuationGroupSupported ); } it( 'should not show panel for marker in the middle of other word', () => { @@ -1725,16 +1723,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; - } } );