context.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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( 'locale', () => {
  44. it( 'is instantiated and t() is exposed', () => {
  45. const context = new Context();
  46. expect( context.locale ).to.be.instanceof( Locale );
  47. expect( context.t ).to.equal( context.locale.t );
  48. } );
  49. it( 'is configured with the config.language (UI and the content)', () => {
  50. const context = new Context( { language: 'pl' } );
  51. expect( context.locale.uiLanguage ).to.equal( 'pl' );
  52. expect( context.locale.contentLanguage ).to.equal( 'pl' );
  53. } );
  54. it( 'is configured with the config.language (different for UI and the content)', () => {
  55. const context = new Context( { language: { ui: 'pl', content: 'ar' } } );
  56. expect( context.locale.uiLanguage ).to.equal( 'pl' );
  57. expect( context.locale.contentLanguage ).to.equal( 'ar' );
  58. } );
  59. it( 'is configured with the config.language (just the content)', () => {
  60. const context = new Context( { language: { content: 'ar' } } );
  61. expect( context.locale.uiLanguage ).to.equal( 'en' );
  62. expect( context.locale.contentLanguage ).to.equal( 'ar' );
  63. } );
  64. } );
  65. describe( 'plugins', () => {
  66. it( 'should throw when plugin added to the context is not marked as a ContextPlugin (Plugin)', async () => {
  67. class EditorPlugin extends Plugin {}
  68. let caughtError;
  69. try {
  70. await Context.create( { plugins: [ EditorPlugin ] } );
  71. } catch ( error ) {
  72. caughtError = error;
  73. }
  74. expect( caughtError ).to.instanceof( CKEditorError );
  75. expect( caughtError.message )
  76. .match( /^context-initplugins-invalid-plugin:/ );
  77. } );
  78. it( 'should throw when plugin added to the context is not marked as a ContextPlugin (Function)', async () => {
  79. function EditorPlugin() {}
  80. let caughtError;
  81. try {
  82. await Context.create( { plugins: [ EditorPlugin ] } );
  83. } catch ( error ) {
  84. caughtError = error;
  85. }
  86. expect( caughtError ).to.instanceof( CKEditorError );
  87. expect( caughtError.message )
  88. .match( /^context-initplugins-invalid-plugin:/ );
  89. } );
  90. it( 'should throw when plugin is added to the context by name', async () => {
  91. let caughtError;
  92. try {
  93. await Context.create( { plugins: [ 'ContextPlugin' ] } );
  94. } catch ( error ) {
  95. caughtError = error;
  96. }
  97. expect( caughtError ).to.instanceof( CKEditorError );
  98. expect( caughtError.message )
  99. .match( /^context-initplugins-constructor-only:/ );
  100. } );
  101. it( 'should not throw when plugin as a function, marked as a ContextPlugin is added to the context', async () => {
  102. function EditorPlugin() {}
  103. EditorPlugin.isContextPlugin = true;
  104. let caughtError;
  105. try {
  106. await Context.create( { plugins: [ EditorPlugin ] } );
  107. } catch ( error ) {
  108. caughtError = error;
  109. }
  110. expect( caughtError ).to.equal( undefined );
  111. } );
  112. it( 'should share the same instance of plugin within editors using the same context', async () => {
  113. class ContextPluginA extends ContextPlugin {}
  114. class ContextPluginB extends ContextPlugin {}
  115. class EditorPluginA extends Plugin {}
  116. const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
  117. const editorA = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA, EditorPluginA ] } );
  118. const editorB = await VirtualTestEditor.create( { context, plugins: [ ContextPluginB, EditorPluginA ] } );
  119. expect( editorA.plugins.get( ContextPluginA ) ).to.equal( context.plugins.get( ContextPluginA ) );
  120. expect( editorA.plugins.has( ContextPluginB ) ).to.equal( false );
  121. expect( editorB.plugins.get( ContextPluginB ) ).to.equal( context.plugins.get( ContextPluginB ) );
  122. expect( editorB.plugins.has( ContextPluginA ) ).to.equal( false );
  123. expect( context.plugins.has( EditorPluginA ) ).to.equal( false );
  124. expect( editorA.plugins.get( EditorPluginA ) ).to.not.equal( editorB.plugins.get( EditorPluginA ) );
  125. await context.destroy();
  126. } );
  127. it( 'should share the same instance of plugin (dependencies) within editors using the same context', async () => {
  128. class ContextPluginA extends ContextPlugin {}
  129. class ContextPluginB extends ContextPlugin {}
  130. class EditorPluginA extends Plugin {
  131. static get requires() {
  132. return [ ContextPluginA ];
  133. }
  134. }
  135. class EditorPluginB extends Plugin {
  136. static get requires() {
  137. return [ ContextPluginB ];
  138. }
  139. }
  140. const context = await Context.create( { plugins: [ ContextPluginA, ContextPluginB ] } );
  141. const editorA = await VirtualTestEditor.create( { context, plugins: [ EditorPluginA ] } );
  142. const editorB = await VirtualTestEditor.create( { context, plugins: [ EditorPluginB ] } );
  143. expect( context.plugins.get( ContextPluginA ) ).to.equal( editorA.plugins.get( ContextPluginA ) );
  144. expect( context.plugins.get( ContextPluginB ) ).to.equal( editorB.plugins.get( ContextPluginB ) );
  145. await context.destroy();
  146. } );
  147. it( 'should not initialize twice plugin added to the context and the editor', async () => {
  148. const initSpy = sinon.spy();
  149. const afterInitSpy = sinon.spy();
  150. class ContextPluginA extends ContextPlugin {
  151. init() {
  152. initSpy();
  153. }
  154. afterInit() {
  155. afterInitSpy();
  156. }
  157. }
  158. const context = await Context.create( { plugins: [ ContextPluginA ] } );
  159. const editor = await VirtualTestEditor.create( { context, plugins: [ ContextPluginA ] } );
  160. expect( context.plugins.get( ContextPluginA ) ).to.equal( editor.plugins.get( ContextPluginA ) );
  161. sinon.assert.calledOnce( initSpy );
  162. sinon.assert.calledOnce( afterInitSpy );
  163. await context.destroy();
  164. } );
  165. it( 'should be able to add context plugin to the editor using pluginName property', async () => {
  166. class ContextPluginA extends ContextPlugin {
  167. static get pluginName() {
  168. return 'ContextPluginA';
  169. }
  170. }
  171. class ContextPluginB extends ContextPlugin {
  172. static get pluginName() {
  173. return 'ContextPluginB';
  174. }
  175. static get requires() {
  176. return [ ContextPluginA ];
  177. }
  178. }
  179. const context = await Context.create( { plugins: [ ContextPluginB ] } );
  180. const editor = await VirtualTestEditor.create( { context, plugins: [ 'ContextPluginA' ] } );
  181. expect( editor.plugins.has( ContextPluginA ) ).to.equal( true );
  182. expect( editor.plugins.has( ContextPluginB ) ).to.equal( false );
  183. } );
  184. } );
  185. describe( 'destroy()', () => {
  186. it( 'should destroy plugins', async () => {
  187. const context = await Context.create();
  188. const spy = sinon.spy( context.plugins, 'destroy' );
  189. await context.destroy();
  190. sinon.assert.calledOnce( spy );
  191. } );
  192. it( 'should destroy all editors with injected context', async () => {
  193. const context = await Context.create();
  194. const editorA = await VirtualTestEditor.create( { context } );
  195. const editorB = await VirtualTestEditor.create( { context } );
  196. const editorC = await VirtualTestEditor.create();
  197. sinon.spy( editorA, 'destroy' );
  198. sinon.spy( editorB, 'destroy' );
  199. sinon.spy( editorC, 'destroy' );
  200. await context.destroy();
  201. sinon.assert.calledOnce( editorA.destroy );
  202. sinon.assert.calledOnce( editorB.destroy );
  203. sinon.assert.notCalled( editorC.destroy );
  204. } );
  205. } );
  206. } );