Przeglądaj źródła

Introduced new configuration options: 'providers', 'extraProviders' and 'removeProviders'.

Aleksander Nowodzinski 7 lat temu
rodzic
commit
25bb0750b3

+ 144 - 72
packages/ckeditor5-media-embed/src/mediaembed.js

@@ -41,6 +41,62 @@ export default class MediaEmbed extends Plugin {
 	}
 }
 
+/**
+ * The media embed provider descriptor. Used in
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#providers `config.mediaEmbed.providers`} and
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}.
+ *
+ * See {@link module:media-embed/mediaembed~MediaEmbedConfig} to learn more.
+ *
+ *		{
+ *			name: 'example',
+ *
+ *			// The following RegExp matches https://www.example.com/media/{media id}
+ *			// with optional "https://" and "www" prefixes.
+ *			url: /^(https:\/\/)?(www\.)?example\.com\/media\/(\w+)/,
+ *
+ *			// The rendering function of the provider.
+ *			// Used to represent the media when editing the content (i.e. in the view)
+ *			// and also in the data output of the editor if semantic data output is disabled.
+ *			html: mediaId => `The HTML representing the media with ID=${ mediaId }.`
+ *		}
+ *
+ * You can allow any sort of media in the editor using the "allow–all" `RegExp`.
+ * But mind that, since URLs are processed in the order of configuration, if one of the previous
+ * `RegExps` matches the URL, it will have a precedence over this one.
+ *
+ *		{
+ *			name: 'allow-all',
+ *			url: /^(https:\/\/)?(www\.)?.+/
+ *		}
+ *
+ * To implement a responsive media, you can use the following HTML structure:
+ *
+ *		{
+ *			...
+ *			html: mediaId =>
+ *				'<div style="position:relative; padding-bottom:100%; height:0">' +
+ *					'<iframe src="..." frameborder="0" ' +
+ *						'style="position:absolute; width:100%; height:100%; top:0; left:0">' +
+ *					'</iframe>' +
+ *				'</div>'
+ *		}
+ *
+ * @typedef {Object} module:media-embed/mediaembed~MediaEmbedProvider
+ * @property {String} name The name of the provider. Used e.g. when
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#removeProviders removing providers}.
+ * @property {RegExp|Array.<RegExp>} url The `RegExp` object (or array of objects) defining the URL of the media.
+ * If any URL matches the `RegExp`, it becomes the media in editor model, as defined by the provider. The content
+ * of the last matching group is passed to the `html` rendering function of the media.
+ * @property {Function} [html] (optional) Rendering function of the media. The function receives the content of
+ * the last matching group from the corresponding `url` `RegExp` as an argument, allowing rendering a dedicated
+ * preview of a media identified by a certain id or a hash. When not defined, the media embed feature
+ * will use a generic media representation in the view and output data.
+ * Note that when
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#semanticDataOutput `config.mediaEmbed.semanticDataOutput`}
+ * is `true`, the rendering function **will not** be used for the media in the editor data output.
+ */
+
 /**
  * The configuration of the {@link module:media-embed/mediaembed~MediaEmbed} feature.
  *
@@ -50,7 +106,8 @@ export default class MediaEmbed extends Plugin {
  */
 
 /**
- * The configuration of the media embed features. Used by the media embed features in the `@ckeditor/ckeditor5-media-embed` package.
+ * The configuration of the media embed features.
+ * Used by the media embed features in the `@ckeditor/ckeditor5-media-embed` package.
  *
  *		ClassicEditor
  *			.create( editorElement, {
@@ -65,85 +122,100 @@ export default class MediaEmbed extends Plugin {
  */
 
 /**
- * The available media providers. By default, the following providers are supported
- * by the editor:
+ * The default media providers supported by the editor.
+ *
+ * Names of providers with rendering functions (previews):
  *
- * With media previews:
+ * * "dailymotion",
+ * * "spotify",
+ * * "youtube",
+ * * "vimeo"
  *
- * * Dailymotion
- * * Spotify
- * * YouTube
- * * Vimeo
+ * Names of providers without rendering functions:
  *
- * Without media previews:
+ * * "instagram",
+ * * "twitter",
+ * * "google",
+ * * "flickr",
+ * * "facebook"
  *
- * * Instagram
- * * Twitter
- * * Google Maps
- * * Flickr
- * * Facebook
+ * See the {@link module:media-embed/mediaembed~MediaEmbedProvider provider syntax} to learn more about
+ * different kinds of media and media providers.
  *
- * **Note:** The default media provider configuration may not support all possible media URLs,
+ * **Note**: The default media provider configuration may not support all possible media URLs,
  * only the most common are included.
  *
- * Media providers can be configured as an array of URL `RegExp` patterns with optional
- * preview rendering functions. The media URLs are processed according to the order of configuration
- * creating as a cascade: the URL is matched against all `RegExp` patterns until one of them passes
- * and allows the media into the editor (and optionally defines its preview).
- * If the URL matches none of the `RegExp` patterns, it is not allowed in the editor.
+ * **Note**: Media without are always represented in the data using the "semantic" markup. See
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#semanticDataOutput `config.mediaEmbed.semanticDataOutput`} to
+ * learn more about possible data outputs.
  *
- * The preview functions accept the content of the last matching group from the corresponding `RegExp`
- * as an argument, allowing rendering a dedicated preview of a media identified by a certain id or a hash.
+ * **Note:**: The priority of media providers corresponds to the order of configuration. The first provider
+ * to match the URL is always used, even if there are other providers which support a particular URL.
+ * The URL is never matched against remaining providers.
  *
- * **Note:**: Preview–less media are always represented in the data using the "semantic" markup. See
- * {@link module:media-embed/mediaembed~MediaEmbedConfig#semanticDataOutput `semanticDataOutput`} to
- * learn more about possible data outputs.
+ * To discard **all** default media providers, simply override this config with your own
+ * {@link module:media-embed/mediaembed~MediaEmbedProvider definitions}:
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ *				mediaEmbed: {
+ *					providers: [
+ *						{
+ *							 name: 'myProvider',
+ *							 url: /^(https:\/\/)?(www\.)?example\.com\/media\/(\w+)/,
+ *							 html: mediaId => '...'
+ *						},
+ *						...
+ * 					]
+ *				}
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * To **extend** the list of default providers, use
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}.
+ *
+ * To **remove** certain providers, use
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#removeProviders `config.mediaEmbed.premoveProviders`}.
+ *
+ * @member {Array.<module:media-embed/mediaembed~MediaEmbedProvider>} module:media-embed/mediaembed~MediaEmbedConfig#providers
+ */
+
+/**
+ * The additional media providers supported by the editor. This configuration helps extend the default
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#providers}.
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ *				mediaEmbed: {
+ *					extraProviders: [
+ *						{
+ *							 name: 'extraProvider',
+ *							 url: /^(https:\/\/)?(www\.)?example\.com\/media\/(\w+)/,
+ *							 html: mediaId => '...'
+ *						},
+ *						...
+ * 					]
+ *				}
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See the {@link module:media-embed/mediaembed~MediaEmbedProvider provider syntax} to learn more.
+ *
+ * @member {Array.<module:media-embed/mediaembed~MediaEmbedProvider>} module:media-embed/mediaembed~MediaEmbedConfig#extraProviders
+ */
+
+/**
+ * The list of media providers which should not be used despite being available in
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#providers `config.mediaEmbed.providers`} and
+ * {@link module:media-embed/mediaembed~MediaEmbedConfig#extraProviders `config.mediaEmbed.extraProviders`}
+ *
+ *		mediaEmbed: {
+ *			removeProviders: [ 'youtube', 'twitter' ]
+ *		}
  *
- *		media: [
- *			{
- *				// The following RegExp definitions support the following URLs:
- *				//
- *				// https://www.ckeditor.com/some/path/{number}
- *				// https://www.ckeditor.com/another/path/{number}
- *				//
- *				// with optional "https://" and "www" prefixes.
- *				url: [
- *					/^(https:\/\/)?(www\.)?ckeditor\.com\/some\/path\/(\d+)/,
- *					/^(https:\/\/)?(www\.)?ckeditor\.com\/another\/path\/(\d+)/
- *				],
- *				// The preview rendering function for the media. It will be always used when
- *				// editing the content (view) and, if config.media.semanticDataOutput is "false", also
- *				// in the data output of the editor.
- *				html: mediaId =>
- *					// Create an optional responsive container for the media.
- *					'<div style="position: relative; padding-bottom: 100%; height: 0; ">' +
- *						// The preview rendered using the mediaId value as defined in the RegExp.
- *						// Usually an iframe, which scales along with the responsive wrapper, e.g.:
- *						`<iframe src="http://ckeditor.com/media/preview/${ mediaId }" ` +
- *							'frameborder="0" width="480" height="270" ' +
- *							'style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;">' +
- *						'</iframe>' +
- *					'</div>'
- *			},
- *
- *			// Another media with the preview.
- *			{
- *				...
- *			},
- *
- *			// This RegExp will allow all media URL starting with https://cksource.com.
- *			// Those media will have no preview and will be displayed with as generic media.
- *			// Generic media are always represented in the output using the semantic HTML markup.
- *			// See config.media.semanticDataOutput to learn more about media representation in the data.
- *			/^(https:\/\/)?cksource\.com/,
- *
- *			// You can allow any sort of media in the editor using the "allow–all" wildcard RegExp.
- *			// Note that, since media are processed in the order of configuration so if one of the previous
- *			// RegExp matches the URL, it will have a precedence over this one.
- *			/^(https:\/\/)?(www\.)?.+/
- *		]
- *
- * @member {Array} module:media-embed/mediaembed~MediaEmbedConfig#media
+ * @member {Array.<String>} module:media-embed/mediaembed~MediaEmbedConfig#removeProviders
  */
 
 /**
@@ -167,8 +239,8 @@ export default class MediaEmbed extends Plugin {
  *
  * **Note:** Preview–less media are always represented in the data using the "semantic" markup
  * regardless of the value of the `semanticDataOutput`. Learn more about different kinds of media
- * in the {@link module:media-embed/mediaembed~MediaEmbedConfig#media `media`} configuration
- * description.
+ * in the {@link module:media-embed/mediaembed~MediaEmbedConfig#providers `config.mediaEmbed.providers`}
+ * configuration description.
  *
  * @member {Boolean} [module:media-embed/mediaembed~MediaEmbedConfig#semanticDataOutput=false]
  */

+ 27 - 10
packages/ckeditor5-media-embed/src/mediaembedediting.js

@@ -29,11 +29,10 @@ export default class MediaEmbedEditing extends Plugin {
 		super( editor );
 
 		editor.config.define( 'mediaEmbed', {
-			media: [
+			providers: [
 				{
-					url: [
-						/^(https:\/\/)?(www\.)?dailymotion\.com\/video\/(\w+)/
-					],
+					name: 'dailymotion',
+					url: /^(https:\/\/)?(www\.)?dailymotion\.com\/video\/(\w+)/,
 					html: id =>
 						'<div style="position: relative; padding-bottom: 100%; height: 0; ">' +
 							`<iframe src="https://www.dailymotion.com/embed/video/${ id }" ` +
@@ -44,6 +43,7 @@ export default class MediaEmbedEditing extends Plugin {
 				},
 
 				{
+					name: 'spotify',
 					url: [
 						/^(https:\/\/)?(www\.)?open\.spotify\.com\/(artist\/\w+)/,
 						/^(https:\/\/)?(www\.)?open\.spotify\.com\/(album\/\w+)/,
@@ -59,6 +59,7 @@ export default class MediaEmbedEditing extends Plugin {
 				},
 
 				{
+					name: 'youtube',
 					url: [
 						/^(https:\/\/)?(www\.)?youtube\.com\/watch\?v=(\w+)/,
 						/^(https:\/\/)?(www\.)?youtube\.com\/v\/(\w+)/,
@@ -75,6 +76,7 @@ export default class MediaEmbedEditing extends Plugin {
 				},
 
 				{
+					name: 'vimeo',
 					url: [
 						/^(https:\/\/)?(www\.)?vimeo\.com\/(\d+)/,
 						/^(https:\/\/)?(www\.)?vimeo\.com\/[^/]+\/[^/]+\/video\/(\d+)/,
@@ -93,11 +95,26 @@ export default class MediaEmbedEditing extends Plugin {
 						'</div>'
 				},
 
-				/^(https:\/\/)?(www\.)?instagram\.com\/p\/(\w+)/,
-				/^(https:\/\/)?(www\.)?twitter\.com/,
-				/^(https:\/\/)?(www\.)?google\.com\/maps/,
-				/^(https:\/\/)?(www\.)?flickr\.com/,
-				/^(https:\/\/)?(www\.)?facebook\.com/
+				{
+					name: 'instagram',
+					url: /^(https:\/\/)?(www\.)?instagram\.com\/p\/(\w+)/
+				},
+				{
+					name: 'twitter',
+					url: /^(https:\/\/)?(www\.)?twitter\.com/
+				},
+				{
+					name: 'google',
+					url: /^(https:\/\/)?(www\.)?google\.com\/maps/
+				},
+				{
+					name: 'flickr',
+					url: /^(https:\/\/)?(www\.)?flickr\.com/
+				},
+				{
+					name: 'facebook',
+					url: /^(https:\/\/)?(www\.)?facebook\.com/
+				},
 			]
 		} );
 
@@ -106,7 +123,7 @@ export default class MediaEmbedEditing extends Plugin {
 		 *
 		 * @member {module:media-embed/mediaregistry~MediaRegistry} #mediaRegistry
 		 */
-		this.mediaRegistry = new MediaRegistry( editor.locale, editor.config.get( 'mediaEmbed.media' ) );
+		this.mediaRegistry = new MediaRegistry( editor.locale, editor.config.get( 'mediaEmbed' ) );
 	}
 
 	/**

+ 28 - 2
packages/ckeditor5-media-embed/src/mediaregistry.js

@@ -8,6 +8,7 @@
  */
 
 import mediaPlaceholderIcon from '../theme/icons/media-placeholder.svg';
+import log from '@ckeditor/ckeditor5-utils/src/log';
 
 /**
  * A bridge between the raw media content provider definitions and editor view content.
@@ -21,9 +22,34 @@ export class MediaRegistry {
 	 * Creates an instance of the {@link module:media-embed/mediaregistry~MediaRegistry} class.
 	 *
 	 * @param {module:utils/locale~Locale} locale The localization services instance.
-	 * @param {Array} providerDefinitions The provider definitions available in this registry.
+	 * @param {module:media-embed/mediaembed~MediaEmbedConfig} config The configuration of the media embed feature.
 	 */
-	constructor( locale, providerDefinitions ) {
+	constructor( locale, config ) {
+		const providers = config.providers || [];
+		const extraProviders = config.extraProviders || [];
+		const removedProviders = new Set( config.removeProviders );
+		const providerDefinitions = providers
+			.concat( extraProviders )
+			.filter( provider => {
+				const name = provider.name;
+
+				if ( !name ) {
+					/**
+					 * One of the providers (or extraProviders) specified in the mediaEmbed configuration
+					 * has no name and will not be used by the editor. In order to get this media
+					 * provider working, double check your editor configuration.
+					 *
+					 * @warning media-embed-no-provider-name
+					 */
+					log.warn( 'media-embed-no-provider-name: The configured media provider has no name and cannot be used.',
+						{ provider } );
+
+					return false;
+				}
+
+				return !removedProviders.has( name );
+			} );
+
 		/**
 		 * The locale {@link module:utils/locale~Locale} instance.
 		 *

+ 443 - 247
packages/ckeditor5-media-embed/tests/mediaembedediting.js

@@ -7,220 +7,440 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest
 import MediaEmbedEditing from '../src/mediaembedediting';
 import { setData as setModelData, getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
-import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
 import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import log from '@ckeditor/ckeditor5-utils/src/log';
 
 describe( 'MediaEmbedEditing', () => {
 	let editor, model, doc, view;
-	const mediaDefinitions = [
-		/^https:\/\/generic/,
-		{
-			url: /(.*)/,
-			html: id => `<iframe src="${ id }"></iframe>`
-		}
-	];
 
 	testUtils.createSinonSandbox();
 
+	const testProviders = {
+		A: {
+			name: 'A',
+			url: /^foo\.com\/(\w+)/,
+			html: id => `A, id=${ id }`
+		},
+		B: {
+			name: 'B',
+			url: /^bar\.com\/(\w+)/,
+			html: id => `B, id=${ id }`
+		},
+		C: {
+			name: 'C',
+			url: /^\w+\.com\/(\w+)/,
+			html: id => `C, id=${ id }`
+		},
+
+		extraA: {
+			name: 'extraA',
+			url: /^foo\.com\/(\w+)/,
+			html: id => `extraA, id=${ id }`
+		},
+		extraB: {
+			name: 'extraB',
+			url: /^\w+\.com\/(\w+)/,
+			html: id => `extraB, id=${ id }`
+		},
+
+		previewLess: {
+			name: 'preview-less',
+			url: /^https:\/\/preview-less/
+		},
+		allowEverything: {
+			name: 'allow-everything',
+			url: /(.*)/,
+			html: id => `allow-everything, id=${ id }`
+		}
+	};
+
 	describe( 'constructor()', () => {
-		describe( 'config.mediaEmbed.media', () => {
-			beforeEach( () => {
-				return VirtualTestEditor
-					.create( {
-						plugins: [ MediaEmbedEditing ],
-						mediaEmbed: {
-							semanticDataOutput: true
-						}
-					} )
-					.then( newEditor => {
-						editor = newEditor;
-						view = editor.editing.view;
+		describe( 'configuration', () => {
+			describe( '#providers', () => {
+				it( 'should warn when provider has no name', () => {
+					const logSpy = testUtils.sinon.stub( log, 'warn' );
+					const provider = {
+						url: /.*/
+					};
+
+					return createTestEditor( {
+						providers: [ provider ]
+					} ).then( () => {
+						expect( logSpy.calledOnce ).to.equal( true );
+						expect( logSpy.firstCall.args[ 0 ] ).to.match( /^media-embed-no-provider-name:/ );
+						expect( logSpy.firstCall.args[ 1 ].provider ).to.equal( provider );
 					} );
-			} );
+				} );
+
+				it( 'can override all providers', () => {
+					return createTestEditor( {
+						providers: []
+					} ).then( editor => {
+						editor.setData( '<figure class="media"><div data-oembed-url="foo.com"></div></figure>' );
 
-			describe( 'with preview', () => {
-				it( 'upcasts the URL (dailymotion)', () => {
-					testMediaUpcast( [
-						'https://www.dailymotion.com/video/foo',
-						'www.dailymotion.com/video/foo',
-						'dailymotion.com/video/foo',
-					],
-					'<div style="position: relative; padding-bottom: 100%; height: 0; ">' +
-						'<iframe src="https://www.dailymotion.com/embed/video/foo" ' +
-							'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
-							'frameborder="0" width="480" height="270" allowfullscreen="" allow="autoplay">' +
-						'</iframe>' +
-					'</div>' );
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal( '' );
+					} );
 				} );
 
-				describe( 'spotify', () => {
-					it( 'upcasts the URL (artist)', () => {
-						testMediaUpcast( [
-							'https://www.open.spotify.com/artist/foo',
-							'www.open.spotify.com/artist/foo',
-							'open.spotify.com/artist/foo',
-						],
-						'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
-							'<iframe src="https://open.spotify.com/embed/artist/foo" ' +
-								'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
-								'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
-							'</iframe>' +
-						'</div>' );
+				it( 'upcast media according to the order', () => {
+					return createTestEditor( {
+						providers: [
+							testProviders.A,
+							testProviders.B,
+							testProviders.C,
+						]
+					} ).then( editor => {
+						editor.setData( '<figure class="media"><div data-oembed-url="foo.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="foo.com/123">' +
+									'A, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+
+						editor.setData( '<figure class="media"><div data-oembed-url="bar.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="bar.com/123">' +
+									'B, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+
+						editor.setData( '<figure class="media"><div data-oembed-url="anything.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="anything.com/123">' +
+									'C, id=123' +
+								'</div>' +
+							'</figure>'
+						);
 					} );
+				} );
 
-					it( 'upcasts the URL (album)', () => {
-						testMediaUpcast( [
-							'https://www.open.spotify.com/album/foo',
-							'www.open.spotify.com/album/foo',
-							'open.spotify.com/album/foo',
-						],
-						'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
-							'<iframe src="https://open.spotify.com/embed/album/foo" ' +
-								'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
-								'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
-							'</iframe>' +
-						'</div>' );
+				describe( 'default value', () => {
+					beforeEach( () => {
+						return createTestEditor( {
+							semanticDataOutput: true
+						} )
+							.then( newEditor => {
+								editor = newEditor;
+								view = editor.editing.view;
+							} );
 					} );
 
-					it( 'upcasts the URL (track)', () => {
-						testMediaUpcast( [
-							'https://www.open.spotify.com/track/foo',
-							'www.open.spotify.com/track/foo',
-							'open.spotify.com/track/foo',
-						],
-						'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
-							'<iframe src="https://open.spotify.com/embed/track/foo" ' +
-								'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
-								'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
-							'</iframe>' +
-						'</div>' );
+					describe( 'with preview', () => {
+						it( 'upcasts the URL (dailymotion)', () => {
+							testMediaUpcast( [
+								'https://www.dailymotion.com/video/foo',
+								'www.dailymotion.com/video/foo',
+								'dailymotion.com/video/foo',
+							],
+							'<div style="position: relative; padding-bottom: 100%; height: 0; ">' +
+								'<iframe src="https://www.dailymotion.com/embed/video/foo" ' +
+									'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
+									'frameborder="0" width="480" height="270" allowfullscreen="" allow="autoplay">' +
+								'</iframe>' +
+							'</div>' );
+						} );
+
+						describe( 'spotify', () => {
+							it( 'upcasts the URL (artist)', () => {
+								testMediaUpcast( [
+									'https://www.open.spotify.com/artist/foo',
+									'www.open.spotify.com/artist/foo',
+									'open.spotify.com/artist/foo',
+								],
+								'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
+									'<iframe src="https://open.spotify.com/embed/artist/foo" ' +
+										'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
+										'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
+									'</iframe>' +
+								'</div>' );
+							} );
+
+							it( 'upcasts the URL (album)', () => {
+								testMediaUpcast( [
+									'https://www.open.spotify.com/album/foo',
+									'www.open.spotify.com/album/foo',
+									'open.spotify.com/album/foo',
+								],
+								'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
+									'<iframe src="https://open.spotify.com/embed/album/foo" ' +
+										'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
+										'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
+									'</iframe>' +
+								'</div>' );
+							} );
+
+							it( 'upcasts the URL (track)', () => {
+								testMediaUpcast( [
+									'https://www.open.spotify.com/track/foo',
+									'www.open.spotify.com/track/foo',
+									'open.spotify.com/track/foo',
+								],
+								'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 126%;">' +
+									'<iframe src="https://open.spotify.com/embed/track/foo" ' +
+										'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
+										'frameborder="0" allowtransparency="true" allow="encrypted-media">' +
+									'</iframe>' +
+								'</div>' );
+							} );
+						} );
+
+						it( 'upcasts the URL (youtube)', () => {
+							testMediaUpcast( [
+								'https://www.youtube.com/watch?v=foo',
+								'www.youtube.com/watch?v=foo',
+								'youtube.com/watch?v=foo',
+
+								'https://www.youtube.com/v/foo',
+								'www.youtube.com/v/foo',
+								'youtube.com/v/foo',
+
+								'https://www.youtube.com/embed/foo',
+								'www.youtube.com/embed/foo',
+								'youtube.com/embed/foo',
+
+								'https://youtu.be/foo',
+								'youtu.be/foo'
+							],
+							'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
+								'<iframe src="https://www.youtube.com/embed/foo" ' +
+									'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
+									'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen="">' +
+								'</iframe>' +
+							'</div>' );
+						} );
+
+						it( 'upcasts the URL (vimeo)', () => {
+							testMediaUpcast( [
+								'https://www.vimeo.com/1234',
+								'www.vimeo.com/1234',
+								'vimeo.com/1234',
+
+								'https://www.vimeo.com/foo/foo/video/1234',
+								'www.vimeo.com/foo/foo/video/1234',
+								'vimeo.com/foo/foo/video/1234',
+
+								'https://www.vimeo.com/album/foo/video/1234',
+								'www.vimeo.com/album/foo/video/1234',
+								'vimeo.com/album/foo/video/1234',
+
+								'https://www.vimeo.com/channels/foo/1234',
+								'www.vimeo.com/channels/foo/1234',
+								'vimeo.com/channels/foo/1234',
+
+								'https://www.vimeo.com/groups/foo/videos/1234',
+								'www.vimeo.com/groups/foo/videos/1234',
+								'vimeo.com/groups/foo/videos/1234',
+
+								'https://www.vimeo.com/ondemand/foo/1234',
+								'www.vimeo.com/ondemand/foo/1234',
+								'vimeo.com/ondemand/foo/1234',
+
+								'https://www.player.vimeo.com/video/1234',
+								'www.player.vimeo.com/video/1234',
+								'player.vimeo.com/video/1234'
+							],
+							'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
+								'<iframe src="https://player.vimeo.com/video/1234" ' +
+									'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
+									'frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="">' +
+								'</iframe>' +
+							'</div>' );
+						} );
 					} );
-				} );
 
-				it( 'upcasts the URL (youtube)', () => {
-					testMediaUpcast( [
-						'https://www.youtube.com/watch?v=foo',
-						'www.youtube.com/watch?v=foo',
-						'youtube.com/watch?v=foo',
-
-						'https://www.youtube.com/v/foo',
-						'www.youtube.com/v/foo',
-						'youtube.com/v/foo',
-
-						'https://www.youtube.com/embed/foo',
-						'www.youtube.com/embed/foo',
-						'youtube.com/embed/foo',
-
-						'https://youtu.be/foo',
-						'youtu.be/foo'
-					],
-					'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
-						'<iframe src="https://www.youtube.com/embed/foo" ' +
-							'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
-							'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen="">' +
-						'</iframe>' +
-					'</div>' );
-				} );
+					describe( 'preview-less', () => {
+						it( 'upcasts the URL (instagram)', () => {
+							testMediaUpcast( [
+								'https://www.instagram.com/p/foo',
+								'www.instagram.com/p/foo',
+								'instagram.com/p/foo',
+							] );
+						} );
+
+						it( 'upcasts the URL (twitter)', () => {
+							testMediaUpcast( [
+								'https://www.twitter.com/foo/bar',
+								'www.twitter.com/foo/bar',
+								'twitter.com/foo/bar',
+							] );
+						} );
+
+						it( 'upcasts the URL (google maps)', () => {
+							testMediaUpcast( [
+								'https://www.google.com/maps/foo',
+								'www.google.com/maps/foo',
+								'google.com/maps/foo',
+							] );
+						} );
+
+						it( 'upcasts the URL (flickr)', () => {
+							testMediaUpcast( [
+								'https://www.flickr.com/foo/bar',
+								'www.flickr.com/foo/bar',
+								'flickr.com/foo/bar',
+							] );
+						} );
 
-				it( 'upcasts the URL (vimeo)', () => {
-					testMediaUpcast( [
-						'https://www.vimeo.com/1234',
-						'www.vimeo.com/1234',
-						'vimeo.com/1234',
-
-						'https://www.vimeo.com/foo/foo/video/1234',
-						'www.vimeo.com/foo/foo/video/1234',
-						'vimeo.com/foo/foo/video/1234',
-
-						'https://www.vimeo.com/album/foo/video/1234',
-						'www.vimeo.com/album/foo/video/1234',
-						'vimeo.com/album/foo/video/1234',
-
-						'https://www.vimeo.com/channels/foo/1234',
-						'www.vimeo.com/channels/foo/1234',
-						'vimeo.com/channels/foo/1234',
-
-						'https://www.vimeo.com/groups/foo/videos/1234',
-						'www.vimeo.com/groups/foo/videos/1234',
-						'vimeo.com/groups/foo/videos/1234',
-
-						'https://www.vimeo.com/ondemand/foo/1234',
-						'www.vimeo.com/ondemand/foo/1234',
-						'vimeo.com/ondemand/foo/1234',
-
-						'https://www.player.vimeo.com/video/1234',
-						'www.player.vimeo.com/video/1234',
-						'player.vimeo.com/video/1234'
-					],
-					'<div style="position: relative; padding-bottom: 100%; height: 0; padding-bottom: 56.2493%;">' +
-						'<iframe src="https://player.vimeo.com/video/1234" ' +
-							'style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;" ' +
-							'frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen="">' +
-						'</iframe>' +
-					'</div>' );
+						it( 'upcasts the URL (facebook)', () => {
+							testMediaUpcast( [
+								'https://www.facebook.com/foo/bar',
+								'www.facebook.com/foo/bar',
+								'facebook.com/foo/bar',
+							] );
+						} );
+					} );
 				} );
 			} );
 
-			describe( 'preview-less', () => {
-				it( 'upcasts the URL (instagram)', () => {
-					testMediaUpcast( [
-						'https://www.instagram.com/p/foo',
-						'www.instagram.com/p/foo',
-						'instagram.com/p/foo',
-					] );
+			describe( '#extraProviders', () => {
+				it( 'should warn when provider has no name', () => {
+					const logSpy = testUtils.sinon.stub( log, 'warn' );
+					const provider = {
+						url: /.*/
+					};
+
+					return createTestEditor( {
+						extraProviders: [ provider ]
+					} ).then( () => {
+						expect( logSpy.calledOnce ).to.equal( true );
+						expect( logSpy.firstCall.args[ 0 ] ).to.match( /^media-embed-no-provider-name:/ );
+						expect( logSpy.firstCall.args[ 1 ].provider ).to.equal( provider );
+					} );
 				} );
 
-				it( 'upcasts the URL (twitter)', () => {
-					testMediaUpcast( [
-						'https://www.twitter.com/foo/bar',
-						'www.twitter.com/foo/bar',
-						'twitter.com/foo/bar',
-					] );
+				it( 'extend #providers but with the lower priority', () => {
+					return createTestEditor( {
+						providers: [
+							testProviders.A,
+						],
+						extraProviders: [
+							testProviders.extraA,
+							testProviders.extraB,
+						]
+					} ).then( editor => {
+						editor.setData( '<figure class="media"><div data-oembed-url="foo.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="foo.com/123">' +
+									'A, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+
+						editor.setData( '<figure class="media"><div data-oembed-url="anything.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="anything.com/123">' +
+									'extraB, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+					} );
 				} );
+			} );
 
-				it( 'upcasts the URL (google maps)', () => {
-					testMediaUpcast( [
-						'https://www.google.com/maps/foo',
-						'www.google.com/maps/foo',
-						'google.com/maps/foo',
-					] );
+			describe( '#removeProviders', () => {
+				it( 'removes #providers', () => {
+					return createTestEditor( {
+						providers: [
+							testProviders.A,
+							testProviders.B
+						],
+						removeProviders: [ 'A' ]
+					} ).then( editor => {
+						editor.setData(
+							'<figure class="media"><div data-oembed-url="foo.com/123"></div></figure>' +
+							'<figure class="media"><div data-oembed-url="bar.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="bar.com/123">' +
+									'B, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+					} );
 				} );
 
-				it( 'upcasts the URL (flickr)', () => {
-					testMediaUpcast( [
-						'https://www.flickr.com/foo/bar',
-						'www.flickr.com/foo/bar',
-						'flickr.com/foo/bar',
-					] );
+				it( 'removes #extraProviders', () => {
+					return createTestEditor( {
+						providers: [],
+						extraProviders: [
+							testProviders.A,
+							testProviders.B,
+						],
+						removeProviders: [ 'A' ]
+					} ).then( editor => {
+						editor.setData(
+							'<figure class="media"><div data-oembed-url="foo.com/123"></div></figure>' +
+							'<figure class="media"><div data-oembed-url="bar.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="bar.com/123">' +
+									'B, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+					} );
 				} );
 
-				it( 'upcasts the URL (facebook)', () => {
-					testMediaUpcast( [
-						'https://www.facebook.com/foo/bar',
-						'www.facebook.com/foo/bar',
-						'facebook.com/foo/bar',
-					] );
+				it( 'removes even when the name of the provider repeats', () => {
+					return createTestEditor( {
+						providers: [
+							testProviders.A,
+							testProviders.A
+						],
+						extraProviders: [
+							testProviders.A,
+							testProviders.A,
+							testProviders.B
+						],
+						removeProviders: [ 'A' ]
+					} ).then( editor => {
+						editor.setData(
+							'<figure class="media"><div data-oembed-url="foo.com/123"></div></figure>' +
+							'<figure class="media"><div data-oembed-url="bar.com/123"></div></figure>' );
+
+						expect( getViewData( editor.editing.view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
+							'<figure class="ck-widget media" contenteditable="false">' +
+								'<div class="ck-media__wrapper" data-oembed-url="bar.com/123">' +
+									'B, id=123' +
+								'</div>' +
+							'</figure>'
+						);
+					} );
 				} );
 			} );
 		} );
 	} );
 
 	describe( 'init()', () => {
+		const providerDefinitions = [
+			testProviders.previewLess,
+			testProviders.allowEverything
+		];
+
 		it( 'should be loaded', () => {
-			return VirtualTestEditor
-				.create( {
-					plugins: [ MediaEmbedEditing ],
-				} )
+			return createTestEditor()
 				.then( newEditor => {
 					expect( newEditor.plugins.get( MediaEmbedEditing ) ).to.be.instanceOf( MediaEmbedEditing );
 				} );
 		} );
 
 		it( 'should set proper schema rules', () => {
-			return VirtualTestEditor
-				.create( {
-					plugins: [ MediaEmbedEditing ],
-				} )
+			return createTestEditor()
 				.then( newEditor => {
 					model = newEditor.model;
 
@@ -238,14 +458,10 @@ describe( 'MediaEmbedEditing', () => {
 		describe( 'conversion in the data pipeline', () => {
 			describe( 'semanticDataOutput=true', () => {
 				beforeEach( () => {
-					return VirtualTestEditor
-						.create( {
-							plugins: [ MediaEmbedEditing ],
-							mediaEmbed: {
-								semanticDataOutput: true,
-								media: mediaDefinitions
-							}
-						} )
+					return createTestEditor( {
+						semanticDataOutput: true,
+						providers: providerDefinitions
+					} )
 						.then( newEditor => {
 							editor = newEditor;
 							model = editor.model;
@@ -273,12 +489,12 @@ describe( 'MediaEmbedEditing', () => {
 							'</figure>' );
 					} );
 
-					it( 'should convert (generic media)', () => {
-						setModelData( model, '<media url="https://generic"></media>' );
+					it( 'should convert (preview-less media)', () => {
+						setModelData( model, '<media url="https://preview-less"></media>' );
 
 						expect( editor.getData() ).to.equal(
 							'<figure class="media">' +
-								'<oembed url="https://generic"></oembed>' +
+								'<oembed url="https://preview-less"></oembed>' +
 							'</figure>' );
 					} );
 				} );
@@ -367,26 +583,19 @@ describe( 'MediaEmbedEditing', () => {
 					} );
 
 					it( 'should not convert unknown media', () => {
-						return VirtualTestEditor
-							.create( {
-								plugins: [ MediaEmbedEditing ],
-								mediaEmbed: {
-									semanticDataOutput: true,
-									media: [
-										{
-											url: /^https:\/\/known\.media/,
-											html: () => 'known-media'
-										}
-									]
-								}
-							} )
+						return createTestEditor( {
+							semanticDataOutput: true,
+							providers: [
+								testProviders.A
+							]
+						} )
 							.then( newEditor => {
 								newEditor.setData(
-									'<figure class="media"><oembed url="https://unknown.media"></oembed></figure>' +
-									'<figure class="media"><oembed url="https://known.media"></oembed></figure>' );
+									'<figure class="media"><oembed url="unknown.media"></oembed></figure>' +
+									'<figure class="media"><oembed url="foo.com/123"></oembed></figure>' );
 
 								expect( getModelData( newEditor.model, { withoutSelection: true } ) )
-									.to.equal( '<media url="https://known.media"></media>' );
+									.to.equal( '<media url="foo.com/123"></media>' );
 
 								return newEditor.destroy();
 							} );
@@ -396,13 +605,9 @@ describe( 'MediaEmbedEditing', () => {
 
 			describe( 'semanticDataOutput=false', () => {
 				beforeEach( () => {
-					return VirtualTestEditor
-						.create( {
-							plugins: [ MediaEmbedEditing ],
-							mediaEmbed: {
-								media: mediaDefinitions
-							}
-						} )
+					return createTestEditor( {
+						providers: providerDefinitions
+					} )
 						.then( newEditor => {
 							editor = newEditor;
 							model = editor.model;
@@ -419,7 +624,7 @@ describe( 'MediaEmbedEditing', () => {
 							expect( editor.getData() ).to.equal(
 								'<figure class="media">' +
 									'<div data-oembed-url="https://ckeditor.com">' +
-										'<iframe src="https://ckeditor.com"></iframe>' +
+										'allow-everything, id=https://ckeditor.com' +
 									'</div>' +
 								'</figure>' );
 						} );
@@ -434,12 +639,12 @@ describe( 'MediaEmbedEditing', () => {
 								'</figure>' );
 						} );
 
-						it( 'should convert (generic media)', () => {
-							setModelData( model, '<media url="https://generic"></media>' );
+						it( 'should convert (preview-less media)', () => {
+							setModelData( model, '<media url="https://preview-less"></media>' );
 
 							expect( editor.getData() ).to.equal(
 								'<figure class="media">' +
-									'<oembed url="https://generic"></oembed>' +
+									'<oembed url="https://preview-less"></oembed>' +
 								'</figure>' );
 						} );
 					} );
@@ -449,7 +654,7 @@ describe( 'MediaEmbedEditing', () => {
 							editor.setData(
 								'<figure class="media">' +
 									'<div data-oembed-url="https://ckeditor.com">' +
-										'<iframe src="https://cksource.com"></iframe>' +
+										'allow-everything, id=https://cksource.com></iframe>' +
 									'</div>' +
 								'</figure>' );
 
@@ -492,7 +697,7 @@ describe( 'MediaEmbedEditing', () => {
 								'<div>' +
 									'<figure class="media">' +
 										'<div data-oembed-url="https://ckeditor.com">' +
-											'<iframe src="https://cksource.com"></iframe>' +
+											'<iframe src="this should be discarded"></iframe>' +
 										'</div>' +
 									'</figure>' +
 								'</div>' );
@@ -511,7 +716,7 @@ describe( 'MediaEmbedEditing', () => {
 								'<div>' +
 									'<figure class="media">' +
 										'<div data-oembed-url="https://ckeditor.com">' +
-											'<iframe src="https://cksource.com"></iframe>' +
+											'<iframe src="this should be discarded"></iframe>' +
 										'</div>' +
 									'</figure>' +
 								'</div>' );
@@ -544,31 +749,22 @@ describe( 'MediaEmbedEditing', () => {
 						} );
 
 						it( 'should not convert unknown media', () => {
-							return VirtualTestEditor
-								.create( {
-									plugins: [ MediaEmbedEditing ],
-									mediaEmbed: {
-										media: [
-											{
-												url: /^https:\/\/known\.media/,
-												html: () => 'known-media'
-											}
-										]
-									}
-								} )
+							return createTestEditor( {
+								providers: [
+									testProviders.A
+								]
+							} )
 								.then( newEditor => {
 									newEditor.setData(
 										'<figure class="media">' +
-											'<div data-oembed-url="https://known.media">' +
-											'</div>' +
+											'<div data-oembed-url="foo.com/123"></div>' +
 										'</figure>' +
 										'<figure class="media">' +
-											'<div data-oembed-url="https://unknown.media">' +
-											'</div>' +
+											'<div data-oembed-url="unknown.media/123"></div>' +
 										'</figure>' );
 
 									expect( getModelData( newEditor.model, { withoutSelection: true } ) )
-										.to.equal( '<media url="https://known.media"></media>' );
+										.to.equal( '<media url="foo.com/123"></media>' );
 
 									return newEditor.destroy();
 								} );
@@ -581,14 +777,10 @@ describe( 'MediaEmbedEditing', () => {
 		describe( 'conversion in the editing pipeline', () => {
 			describe( 'semanticDataOutput=true', () => {
 				beforeEach( () => {
-					return VirtualTestEditor
-						.create( {
-							plugins: [ MediaEmbedEditing ],
-							mediaEmbed: {
-								media: mediaDefinitions,
-								semanticDataOutput: true
-							}
-						} )
+					return createTestEditor( {
+						providers: providerDefinitions,
+						semanticDataOutput: true
+					} )
 						.then( newEditor => {
 							editor = newEditor;
 							model = editor.model;
@@ -602,13 +794,9 @@ describe( 'MediaEmbedEditing', () => {
 
 			describe( 'semanticDataOutput=false', () => {
 				beforeEach( () => {
-					return VirtualTestEditor
-						.create( {
-							plugins: [ MediaEmbedEditing ],
-							mediaEmbed: {
-								media: mediaDefinitions
-							}
-						} )
+					return createTestEditor( {
+						providers: providerDefinitions
+					} )
 						.then( newEditor => {
 							editor = newEditor;
 							model = editor.model;
@@ -628,7 +816,7 @@ describe( 'MediaEmbedEditing', () => {
 						expect( getViewData( view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
 							'<figure class="ck-widget media" contenteditable="false">' +
 								'<div class="ck-media__wrapper" data-oembed-url="https://ckeditor.com">' +
-									'<iframe src="https://ckeditor.com"></iframe>' +
+									'allow-everything, id=https://ckeditor.com' +
 								'</div>' +
 							'</figure>'
 						);
@@ -645,7 +833,7 @@ describe( 'MediaEmbedEditing', () => {
 						expect( getViewData( view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
 							'<figure class="ck-widget media" contenteditable="false">' +
 								'<div class="ck-media__wrapper" data-oembed-url="https://cksource.com">' +
-									'<iframe src="https://cksource.com"></iframe>' +
+									'allow-everything, id=https://cksource.com' +
 								'</div>' +
 							'</figure>'
 						);
@@ -683,7 +871,7 @@ describe( 'MediaEmbedEditing', () => {
 						expect( getViewData( view, { withoutSelection: true, renderUIElements: true } ) ).to.equal(
 							'<figure class="ck-widget media" contenteditable="false">' +
 								'<div class="ck-media__wrapper" data-oembed-url="https://ckeditor.com">' +
-									'<iframe src="https://ckeditor.com"></iframe>' +
+									'allow-everything, id=https://ckeditor.com' +
 								'</div>' +
 							'</figure>'
 						);
@@ -720,4 +908,12 @@ describe( 'MediaEmbedEditing', () => {
 			expect( normalizeHtml( viewData ) ).to.match( expectedRegExp, `assertion for "${ url }"` );
 		}
 	}
+
+	function createTestEditor( mediaEmbedConfig ) {
+		return VirtualTestEditor
+			.create( {
+				plugins: [ MediaEmbedEditing ],
+				mediaEmbed: mediaEmbedConfig
+			} );
+	}
 } );

+ 2 - 1
packages/ckeditor5-media-embed/tests/mediaembedui.js

@@ -21,8 +21,9 @@ describe( 'MediaEmbedUI', () => {
 			.create( editorElement, {
 				plugins: [ MediaEmbed ],
 				mediaEmbed: {
-					media: [
+					providers: [
 						{
+							name: 'valid-media',
 							url: /^https:\/\/valid\/(.*)/,
 							html: id => `<iframe src="${ id }"></iframe>`
 						}