Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge branch 't/44b' into t/44
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/mentionui.js
#	tests/mentionui.js
  • Loading branch information
jodator committed May 10, 2019
2 parents 10a4276 + 7d2f6c4 commit eba5b82
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 107 deletions.
35 changes: 35 additions & 0 deletions src/featuredetection.js
Original file line number Diff line number Diff line change
@@ -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;
}() )
};
20 changes: 6 additions & 14 deletions src/mentionui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down
119 changes: 26 additions & 93 deletions tests/mentionui.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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( () => {
Expand Down Expand Up @@ -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' );
} );
} );

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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;
}
} );

0 comments on commit eba5b82

Please sign in to comment.