context.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import Context from '../src/context';
  6. import ContextPlugin from '../src/contextplugin';
  7. import Plugin from '../src/plugin';
  8. import Config from '@ckeditor/ckeditor5-utils/src/config';
  9. import Locale from '@ckeditor/ckeditor5-utils/src/locale';
  10. import VirtualTestEditor from './_utils/virtualtesteditor';
  11. import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
  12. describe( 'Context', () => {
  13. describe( 'config', () => {
  14. it( 'should be created', () => {
  15. const context = new Context();
  16. expect( context.config ).to.instanceof( Config );
  17. } );
  18. it( 'should be with given configuration', () => {
  19. const context = new Context( { foo: 'bar' } );
  20. expect( context.config.get( 'foo' ) ).to.equal( 'bar' );
  21. } );
  22. } );
  23. describe( '_getEditorConfig()', () => {
  24. it( 'should return the configuration without plugin config', () => {
  25. class FooPlugin extends ContextPlugin {}
  26. class BarPlugin extends ContextPlugin {}
  27. class BomPlugin extends ContextPlugin {}
  28. const context = new Context( {
  29. language: { ui: 'pl', content: 'ar' },
  30. plugins: [ FooPlugin, BarPlugin ],
  31. extraPlugins: [ BomPlugin ],
  32. removePlugins: [ FooPlugin ],
  33. foo: 1,
  34. bar: 'bom'
  35. } );
  36. expect( context._getEditorConfig() ).to.be.deep.equal( {
  37. language: { ui: 'pl', content: 'ar' },
  38. foo: 1,
  39. bar: 'bom'
  40. } );
  41. } );
  42. } );
  43. describe( 'editors', () => {
  44. it( 'should keep all the editors created within the context and fire event:add whenever an editor is added', async () => {
  45. const spy = sinon.spy();
  46. const context = await Context.create();
  47. context.editors.on( 'add', spy );
  48. const editorA = await VirtualTestEditor.create( { context } );
  49. expect( spy.calledOnce );
  50. const editorB = await VirtualTestEditor.create( { context } );
  51. expect( spy.calledTwice );
  52. expect( context.editors.has( editorA ) );
  53. expect( context.editors.has( editorB ) );
  54. } );
  55. } );
  56. describe( 'locale', () => {
  57. it( 'is instantiated and t() is exposed', () => {
  58. const context = new Context();
  59. expect( context.locale ).to.be.instanceof( Locale );
  60. expect( context.t ).to.equal( context.locale.t );
  61. } );
  62. it( 'is configured with the config.language (UI and the content)', () => {
  63. const context = new Context( { language: 'pl' } );
  64. expect( context.locale.uiLanguage ).to.equal( 'pl' );
  65. expect( context.locale.contentLanguage ).to.equal( 'pl' );
  66. } );
  67. it( 'is configured with the config.language (different for UI and the content)', () => {
  68. const context = new Context( { language: { ui: 'pl', content: 'ar' } } );
  69. expect( context.locale.uiLanguage ).to.equal( 'pl' );
  70. expect( context.locale.contentLanguage ).to.equal( 'ar' );
  71. } );
  72. it( 'is configured with the config.language (just the content)', () => {
  73. const context = new Context( { language: { content: 'ar' } } );
  74. expect( context.locale.uiLanguage ).to.equal( 'en' );
  75. expect( context.locale.contentLanguage ).to.equal( 'ar' );
  76. } );
  77. } );
  78. describe( 'plugins', () => {
  79. it( 'should throw when plugin added to the context is not marked as a ContextPlugin (Plugin)', async () => {
  80. class EditorPlugin extends Plugin {}
  81. let caughtError;
  82. try {
  83. await Context.create( { plugins: [ EditorPlugin ] } );
  84. } catch ( error ) {
  85. caughtError = error;
  86. }
  87. expect( caughtError ).to.instanceof( CKEditorError );
  88. expect( caughtError.message )
  89. .match( /^context-initplugins-invalid-plugin:/ );
  90. } );
  91. it( 'should throw when plugin added to the context is not marked as a ContextPlugin (Function)', async () => {
  92. function EditorPlugin() {}
  93. let caughtError;
  94. try {
  95. await Context.create( { plugins: [ EditorPlugin ] } );
  96. } catch ( error ) {
  97. caughtError = error;
  98. }
  99. expect( caughtError ).to.instanceof( CKEditorError );
  100. expect( caughtError.message )
  101. .match( /^context-initplugins-invalid-plugin:/ );
  102. } );
  103. it( 'should throw when plugin is added to the context by name', async () => {
  104. let caughtError;
  105. try {
  106. await Context.create( { plugins: [ 'ContextPlugin' ] } );
  107. } catch ( error ) {
  108. caughtError = error;
  109. }
  110. expect( caughtError ).to.instanceof( CKEditorError );
  111. expect( caughtError.message )
  112. .match( /^context-initplugins-constructor-only:/ );
  113. } );
  114. it( 'should not throw when plugin as a function, marked as a ContextPlugin is added to the context', async () => {
  115. function EditorPlugin() {}
  116. EditorPlugin.isContextPlugin = true;
  117. let caughtError;
  118. try {
  119. await Context.create( { plugins: [ EditorPlugin ] } );
  120. } catch ( error ) {
  121. caughtError = error;
  122. }
  123. expect( caughtError ).to.equal( undefined );
  124. } );
  125. it( 'should share the same instance of plugin within editors using the same context', async () => {
  126. class ContextPluginA extends ContextPlugin {}
  127. class ContextPluginB extends ContextPlugin {}
  128. class EditorPluginA extends Plugin {}
  129. const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
  130. const editorA = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA, EditorPluginA ] } );
  131. const editorB = await VirtualTestEditor.create( { context, plugins: [ ContextPluginB, EditorPluginA ] } );
  132. expect( editorA.plugins.get( ContextPluginA ) ).to.equal( context.plugins.get( ContextPluginA ) );
  133. expect( editorA.plugins.has( ContextPluginB ) ).to.equal( false );
  134. expect( editorB.plugins.get( ContextPluginB ) ).to.equal( context.plugins.get( ContextPluginB ) );
  135. expect( editorB.plugins.has( ContextPluginA ) ).to.equal( false );
  136. expect( context.plugins.has( EditorPluginA ) ).to.equal( false );
  137. expect( editorA.plugins.get( EditorPluginA ) ).to.not.equal( editorB.plugins.get( EditorPluginA ) );
  138. await context.destroy();
  139. } );
  140. it( 'should share the same instance of plugin (dependencies) within editors using the same context', async () => {
  141. class ContextPluginA extends ContextPlugin {}
  142. class ContextPluginB extends ContextPlugin {}
  143. class EditorPluginA extends Plugin {
  144. static get requires() {
  145. return [ ContextPluginA ];
  146. }
  147. }
  148. class EditorPluginB extends Plugin {
  149. static get requires() {
  150. return [ ContextPluginB ];
  151. }
  152. }
  153. const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
  154. const editorA = await VirtualTestEditor.create( { context, plugins: [ EditorPluginA ] } );
  155. const editorB = await VirtualTestEditor.create( { context, plugins: [ EditorPluginB ] } );
  156. expect( context.plugins.get( ContextPluginA ) ).to.equal( editorA.plugins.get( ContextPluginA ) );
  157. expect( context.plugins.get( ContextPluginB ) ).to.equal( editorB.plugins.get( ContextPluginB ) );
  158. await context.destroy();
  159. } );
  160. it( 'should not initialize twice plugin added to the context and the editor', async () => {
  161. const initSpy = sinon.spy();
  162. const afterInitSpy = sinon.spy();
  163. class ContextPluginA extends ContextPlugin {
  164. init() {
  165. initSpy();
  166. }
  167. afterInit() {
  168. afterInitSpy();
  169. }
  170. }
  171. const context = await Context.create( { plugins: [ ContextPluginA ] } );
  172. const editor = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA ] } );
  173. expect( context.plugins.get( ContextPluginA ) ).to.equal( editor.plugins.get( ContextPluginA ) );
  174. sinon.assert.calledOnce( initSpy );
  175. sinon.assert.calledOnce( afterInitSpy );
  176. await context.destroy();
  177. } );
  178. it( 'should be able to add context plugin to the editor using pluginName property', async () => {
  179. class ContextPluginA extends ContextPlugin {
  180. static get pluginName() {
  181. return 'ContextPluginA';
  182. }
  183. }
  184. class ContextPluginB extends ContextPlugin {
  185. static get pluginName() {
  186. return 'ContextPluginB';
  187. }
  188. static get requires() {
  189. return [ ContextPluginA ];
  190. }
  191. }
  192. const context = await Context.create( { plugins: [ ContextPluginB ] } );
  193. const editor = await VirtualTestEditor.create( { context, plugins: [ 'ContextPluginA' ] } );
  194. expect( editor.plugins.has( ContextPluginA ) ).to.equal( true );
  195. expect( editor.plugins.has( ContextPluginB ) ).to.equal( false );
  196. } );
  197. } );
  198. describe( 'destroy()', () => {
  199. it( 'should destroy plugins', async () => {
  200. const context = await Context.create();
  201. const spy = sinon.spy( context.plugins, 'destroy' );
  202. await context.destroy();
  203. sinon.assert.calledOnce( spy );
  204. } );
  205. it( 'should destroy all editors with injected context', async () => {
  206. const context = await Context.create();
  207. const editorA = await VirtualTestEditor.create( { context } );
  208. const editorB = await VirtualTestEditor.create( { context } );
  209. const editorC = await VirtualTestEditor.create();
  210. sinon.spy( editorA, 'destroy' );
  211. sinon.spy( editorB, 'destroy' );
  212. sinon.spy( editorC, 'destroy' );
  213. await context.destroy();
  214. sinon.assert.calledOnce( editorA.destroy );
  215. sinon.assert.calledOnce( editorB.destroy );
  216. sinon.assert.notCalled( editorC.destroy );
  217. } );
  218. it( 'should not crash when destroyed for the second time', async () => {
  219. const context = await Context.create();
  220. await VirtualTestEditor.create( { context } );
  221. await context.destroy();
  222. await context.destroy();
  223. } );
  224. it( 'should not crash when destroyed for the second time - editor own managed context', async () => {
  225. const editor = await VirtualTestEditor.create();
  226. await editor.destroy();
  227. await editor.destroy();
  228. } );
  229. } );
  230. describe( 'builtinPlugins', () => {
  231. class PluginA extends ContextPlugin {}
  232. class PluginB extends ContextPlugin {}
  233. class PluginC extends ContextPlugin {}
  234. beforeEach( () => {
  235. Context.builtinPlugins = [ PluginA, PluginB, PluginC ];
  236. } );
  237. afterEach( () => {
  238. delete Context.builtinPlugins;
  239. } );
  240. it( 'should load plugins built in the Context even if the passed config is empty', () => {
  241. const context = new Context();
  242. return context.initPlugins()
  243. .then( () => {
  244. expect( getPlugins( context ).length ).to.equal( 3 );
  245. expect( context.plugins.get( PluginA ) ).to.be.an.instanceof( ContextPlugin );
  246. expect( context.plugins.get( PluginB ) ).to.be.an.instanceof( ContextPlugin );
  247. expect( context.plugins.get( PluginC ) ).to.be.an.instanceof( ContextPlugin );
  248. } );
  249. } );
  250. it( 'should load plugins provided in the config and should ignore plugins built in the Editor', () => {
  251. const context = new Context( {
  252. plugins: [
  253. PluginA
  254. ]
  255. } );
  256. return context.initPlugins()
  257. .then( () => {
  258. expect( getPlugins( context ).length ).to.equal( 1 );
  259. expect( context.plugins.get( PluginA ) ).to.be.an.instanceof( ContextPlugin );
  260. } );
  261. } );
  262. } );
  263. describe( 'defaultConfig', () => {
  264. beforeEach( () => {
  265. Context.defaultConfig = { foo: 1, bar: 2 };
  266. } );
  267. afterEach( () => {
  268. delete Context.defaultConfig;
  269. } );
  270. it( 'should extend an editor configuration using built in config', () => {
  271. const context = new Context( {
  272. foo: 4
  273. } );
  274. expect( context.config.get( 'foo' ) ).to.equal( 4 );
  275. expect( context.config.get( 'bar' ) ).to.equal( 2 );
  276. } );
  277. } );
  278. } );
  279. function getPlugins( editor ) {
  280. return Array.from( editor.plugins )
  281. .map( entry => entry[ 1 ] ); // Get instances.
  282. }