/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor'; import CodeBlockEditing from '@ckeditor/ckeditor5-code-block/src/codeblockediting'; import Enter from '@ckeditor/ckeditor5-enter/src/enter'; import Input from '@ckeditor/ckeditor5-typing/src/input'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; import LinkEditing from '../src/linkediting'; import AutoLink from '../src/autolink'; describe( 'AutoLink', () => { let editor; it( 'should be named', () => { expect( AutoLink.pluginName ).to.equal( 'AutoLink' ); } ); it( 'should be loaded without Enter & ShiftEnter features', async () => { const editor = await ModelTestEditor.create( { plugins: [ Paragraph, Input, LinkEditing, AutoLink ] } ); await editor.destroy(); } ); describe( 'auto link behavior', () => { let model; beforeEach( async () => { editor = await ModelTestEditor.create( { plugins: [ Paragraph, Input, Enter, ShiftEnter, LinkEditing, AutoLink ] } ); model = editor.model; setData( model, '[]' ); } ); it( 'does nothing on typing normal text', () => { simulateTyping( 'Cupcake ipsum dolor. Sit amet caramels. Pie jelly-o lemon drops fruitcake.' ); expect( getData( model ) ).to.equal( 'Cupcake ipsum dolor. Sit amet caramels. Pie jelly-o lemon drops fruitcake.[]' ); } ); it( 'does not add linkHref attribute to a text link while typing', () => { simulateTyping( 'https://www.cksource.com' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com[]' ); } ); it( 'adds linkHref attribute to a text link after space', () => { simulateTyping( 'https://www.cksource.com ' ); expect( getData( model ) ).to.equal( '<$text linkHref="https://www.cksource.com">https://www.cksource.com []' ); } ); it( 'does not add linkHref attribute if linkHref is not allowed', () => { model.schema.addAttributeCheck( () => false ); // Disable all attributes. simulateTyping( 'https://www.cksource.com ' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com []' ); } ); it( 'does not add linkHref attribute if plugin is force-disabled (on space)', () => { editor.plugins.get( 'AutoLink' ).forceDisabled( 'test' ); simulateTyping( 'https://www.cksource.com ' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com []' ); } ); it( 'does not add linkHref attribute if plugin is force-disabled (on enter)', () => { setData( model, 'https://www.cksource.com[]' ); editor.plugins.get( 'AutoLink' ).forceDisabled( 'test' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com' + '[]' ); } ); it( 'does not add linkHref attribute if plugin is force-disabled (on shift enter)', () => { setData( model, 'https://www.cksource.com[]' ); editor.plugins.get( 'AutoLink' ).forceDisabled( 'test' ); editor.execute( 'shiftEnter' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com[]' ); } ); it( 'does not add linkHref attribute on enter when the link is selected', () => { setData( model, '[https://www.cksource.com]' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( '[]' ); } ); it( 'does not add linkHref attribute on enter when the whole paragraph containing the link is selected', () => { setData( model, '[This feature also works with e-mail addresses: https://www.cksource.com]' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( '[]' ); } ); it( 'adds linkHref attribute on enter when the link (that contains www) is partially selected (end)', () => { setData( model, 'https://www.ckso[urce.com]' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( '<$text linkHref="https://www.ckso">https://www.ckso[]' ); } ); it( 'does not add linkHref attribute on enter when the link (that does not contain www) is partially selected (end)', () => { setData( model, 'https://ckso[urce.com]' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( 'https://ckso[]' ); } ); it( 'does not add linkHref attribute on enter when the link is partially selected (beginning)', () => { setData( model, '[https://www.ckso]urce.com' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( '[]urce.com' ); } ); it( 'adds linkHref attribute to a text link after space (inside paragraph)', () => { setData( model, 'Foo Bar [] Baz' ); simulateTyping( 'https://www.cksource.com ' ); expect( getData( model ) ).to.equal( 'Foo Bar <$text linkHref="https://www.cksource.com">https://www.cksource.com [] Baz' ); } ); it( 'adds linkHref attribute to a text link on shift enter', () => { setData( model, 'https://www.cksource.com[]' ); editor.execute( 'shiftEnter' ); // TODO: should test with selection but master has a bug. See: https://github.com/ckeditor/ckeditor5/issues/7459. expect( getData( model, { withoutSelection: true } ) ).to.equal( '' + '<$text linkHref="https://www.cksource.com">https://www.cksource.com' + '' + '' ); } ); it( 'does not add linkHref attribute to a text link after double soft break', () => { setData( model, 'https://www.cksource.com[]' ); editor.execute( 'shiftEnter' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com[]' ); } ); it( 'adds linkHref attribute to a text link on enter', () => { setData( model, 'https://www.cksource.com[]' ); editor.execute( 'enter' ); expect( getData( model ) ).to.equal( '' + '<$text linkHref="https://www.cksource.com">https://www.cksource.com' + '' + '[]' ); } ); it( 'adds "mailto:" to link of detected email addresses', () => { simulateTyping( 'newsletter@cksource.com ' ); expect( getData( model ) ).to.equal( '<$text linkHref="mailto:newsletter@cksource.com">newsletter@cksource.com []' ); } ); // Some examples came from https://mathiasbynens.be/demo/url-regex. describe( 'supported URL', () => { const supportedURLs = [ 'http://cksource.com', 'https://cksource.com', 'https://cksource.com:8080', 'http://www.cksource.com', 'hTtP://WwW.cKsOuRcE.cOm', 'www.cksource.com', 'http://foo.bar.cksource.com', 'http://www.cksource.com/some/path/index.html#abc', 'http://www.cksource.com/some/path/index.html?foo=bar', 'http://www.cksource.com/some/path/index.html?foo=bar#abc', 'http://www.cksource.com:8080/some/path/index.html?foo=bar#abc', 'http://www.cksource.com/some/path/index.html#abc?foo=bar', 'ftp://cksource.com', 'http://cksource.com/foo_bar', 'http://cksource.com/foo_bar/', 'http://cksource.com/foo_bar_(wikipedia)', 'http://cksource.com/foo_bar_(wikipedia)_(again)', 'http://www.cksource.com/wpstyle/?p=364', 'http://www.cksource.com/wpstyle/?bar=baz&inga=42&quux', 'http://userid:password@example.com:8080' + 'http://userid:password@example.com:8080/' + 'http://userid@cksource.com' + 'http://userid@cksource.com/' + 'http://userid@cksource.com:8080' + 'http://userid@cksource.com:8080/' + 'http://userid:password@cksource.com' + 'http://userid:password@cksource.com/' + 'http://🥳df.ws/123', 'http://🥳.ws/富', 'http://🥳.ws', 'http://🥳.ws/', 'http://cksource.com/blah_(wikipedia)#cite-1', 'http://cksource.com/blah_(wikipedia)_blah#cite-1', 'http://cksource.com/unicode_(🥳)_in_parens', 'http://cksource.com/(something)?after=parens', 'http://🥳.cksource.com/', 'http://code.cksource.com/woot/#&product=browser', 'http://j.mp', 'ftp://cksource.com/baz', 'http://cksource.com/?q=Test%20URL-encoded%20stuff', 'http://مثال.إختبار', 'http://例子.测试', 'http://उदाहरण.परीक्षा', 'http://1337.net', 'http://a.b-c.de' ]; for ( const supportedURL of supportedURLs ) { it( `should detect "${ supportedURL }" as a valid URL`, () => { simulateTyping( supportedURL + ' ' ); expect( getData( model ) ).to.equal( `<$text linkHref="${ supportedURL }">${ supportedURL } []` ); } ); } } ); describe( 'invalid or supported URL', () => { // Some examples came from https://mathiasbynens.be/demo/url-regex. const unsupportedOrInvalid = [ 'http://', 'http://.', 'http://..', 'http://../', 'http://🥳', 'http://?', 'http://??', 'http://??/', 'http://#', 'http://##', 'http://##/', '//', '//a', '///a', '///', 'http:///a', 'rdar://1234', 'h://test', ':// foo bar', 'ftps://foo.bar/', 'http://-error-.invalid/', 'http://localhost', 'http:/cksource.com', 'cksource.com', 'ww.cksource.com' ]; for ( const unsupportedURL of unsupportedOrInvalid ) { it( `should not detect "${ unsupportedURL }" as a valid URL`, () => { simulateTyping( unsupportedURL + ' ' ); expect( getData( model ) ).to.equal( `${ unsupportedURL } []` ); } ); } } ); } ); describe( 'Undo integration', () => { let model; beforeEach( async () => { editor = await ModelTestEditor.create( { plugins: [ Paragraph, Input, Enter, ShiftEnter, LinkEditing, AutoLink, UndoEditing ] } ); model = editor.model; setData( model, 'https://www.cksource.com[]' ); } ); it( 'should undo auto-linking (after space)', () => { simulateTyping( ' ' ); editor.commands.execute( 'undo' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com []' ); } ); it( 'should undo auto-linking (after )', () => { editor.execute( 'shiftEnter' ); editor.commands.execute( 'undo' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com[]' ); } ); it( 'should undo auto-linking (after enter)', () => { editor.execute( 'enter' ); editor.commands.execute( 'undo' ); expect( getData( model ) ).to.equal( 'https://www.cksource.com' + '[]' ); } ); } ); describe( 'Code blocks integration', () => { let model; beforeEach( async () => { editor = await ModelTestEditor.create( { plugins: [ Paragraph, Input, Enter, ShiftEnter, LinkEditing, AutoLink, CodeBlockEditing ] } ); model = editor.model; } ); it( 'should be disabled inside code blocks (on space)', () => { setData( model, 'some [] code' ); const plugin = editor.plugins.get( 'AutoLink' ); simulateTyping( 'www.cksource.com' ); expect( plugin.isEnabled ).to.be.false; expect( getData( model, { withoutSelection: true } ) ) .to.equal( 'some www.cksource.com code' ); } ); it( 'should be disabled inside code blocks (on enter)', () => { setData( model, 'some www.cksource.com[] code' ); const plugin = editor.plugins.get( 'AutoLink' ); editor.execute( 'enter' ); expect( plugin.isEnabled ).to.be.false; expect( getData( model, { withoutSelection: true } ) ).to.equal( 'some www.cksource.com' + ' code' ); } ); it( 'should be disabled inside code blocks (on shift-enter)', () => { setData( model, 'some www.cksource.com[] code' ); const plugin = editor.plugins.get( 'AutoLink' ); editor.execute( 'shiftEnter' ); expect( plugin.isEnabled ).to.be.false; expect( getData( model, { withoutSelection: true } ) ).to.equal( 'some www.cksource.com code' ); } ); } ); function simulateTyping( text ) { const letters = text.split( '' ); for ( const letter of letters ) { editor.execute( 'input', { text: letter } ); } } } );