ckeditor5 1.20.1 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -2
  3. data/lib/ckeditor5/rails/assets/assets_bundle.rb +11 -1
  4. data/lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb +5 -10
  5. data/lib/ckeditor5/rails/assets/webcomponent_bundle.rb +10 -3
  6. data/lib/ckeditor5/rails/assets/webcomponents/components/editor.mjs +35 -2
  7. data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +36 -2
  8. data/lib/ckeditor5/rails/cdn/concerns/bundle_builder.rb +57 -0
  9. data/lib/ckeditor5/rails/cdn/helpers.rb +62 -55
  10. data/lib/ckeditor5/rails/editor/helpers/editor_helpers.rb +25 -19
  11. data/lib/ckeditor5/rails/editor/props.rb +7 -14
  12. data/lib/ckeditor5/rails/engine.rb +13 -0
  13. data/lib/ckeditor5/rails/presets/manager.rb +2 -0
  14. data/lib/ckeditor5/rails/presets/preset_builder.rb +1 -1
  15. data/lib/ckeditor5/rails/version.rb +1 -1
  16. data/spec/e2e/features/ajax_form_integration_spec.rb +78 -0
  17. data/spec/e2e/features/lazy_assets_spec.rb +54 -0
  18. data/spec/e2e/support/form_helpers.rb +4 -1
  19. data/spec/lib/ckeditor5/rails/assets/assets_bundle_hml_serializer_spec.rb +53 -0
  20. data/spec/lib/ckeditor5/rails/assets/assets_bundle_spec.rb +2 -1
  21. data/spec/lib/ckeditor5/rails/cdn/helpers_spec.rb +95 -3
  22. data/spec/lib/ckeditor5/rails/editor/helpers/editor_helpers_spec.rb +85 -25
  23. data/spec/lib/ckeditor5/rails/editor/props_spec.rb +14 -34
  24. data/spec/lib/ckeditor5/rails/engine_spec.rb +10 -0
  25. data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +15 -2
  26. data/spec/lib/ckeditor5/rails/plugins/wproofreader_spec.rb +2 -0
  27. data/spec/lib/ckeditor5/rails/version_detector_spec.rb +1 -1
  28. metadata +7 -2
@@ -14,16 +14,17 @@ module CKEditor5::Rails::Editor
14
14
  }.freeze
15
15
 
16
16
  def initialize(
17
- controller_context, type, config,
18
- watchdog: true, editable_height: nil, language: nil
17
+ type, config,
18
+ bundle: nil,
19
+ watchdog: true,
20
+ editable_height: nil
19
21
  )
20
22
  raise ArgumentError, "Invalid editor type: #{type}" unless Props.valid_editor_type?(type)
21
23
 
22
- @controller_context = controller_context
24
+ @bundle = bundle
23
25
  @watchdog = watchdog
24
26
  @type = type
25
27
  @config = config
26
- @language = language
27
28
  @editable_height = EditableHeightNormalizer.new(type).normalize(editable_height)
28
29
  end
29
30
 
@@ -40,11 +41,11 @@ module CKEditor5::Rails::Editor
40
41
 
41
42
  private
42
43
 
43
- attr_reader :controller_context, :watchdog, :type, :config, :editable_height
44
+ attr_reader :bundle, :watchdog, :type, :config, :editable_height
44
45
 
45
46
  def serialized_attributes
46
47
  {
47
- translations: serialize_translations,
48
+ bundle: bundle.to_json,
48
49
  plugins: serialize_plugins,
49
50
  config: serialize_config,
50
51
  watchdog: watchdog
@@ -52,10 +53,6 @@ module CKEditor5::Rails::Editor
52
53
  .merge(editable_height ? { 'editable-height' => editable_height } : {})
53
54
  end
54
55
 
55
- def serialize_translations
56
- controller_context[:bundle]&.translations_scripts&.map(&:to_h).to_json || '[]'
57
- end
58
-
59
56
  def serialize_plugins
60
57
  (config[:plugins] || []).map { |plugin| PropsBasePlugin.normalize(plugin).to_h }.to_json
61
58
  end
@@ -63,10 +60,6 @@ module CKEditor5::Rails::Editor
63
60
  def serialize_config
64
61
  config
65
62
  .except(:plugins)
66
- .tap do |cfg|
67
- cfg[:licenseKey] = controller_context[:license_key] if controller_context[:license_key]
68
- cfg[:language] = { ui: @language } if @language
69
- end
70
63
  .to_json
71
64
  end
72
65
  end
@@ -7,6 +7,8 @@ require_relative 'plugins/simple_upload_adapter'
7
7
  require_relative 'plugins/wproofreader'
8
8
 
9
9
  module CKEditor5::Rails
10
+ class PresetNotFoundError < ArgumentError; end
11
+
10
12
  class Engine < ::Rails::Engine
11
13
  config.ckeditor5 = ActiveSupport::OrderedOptions.new
12
14
  config.ckeditor5.presets = Presets::Manager.new
@@ -47,6 +49,10 @@ module CKEditor5::Rails
47
49
  config.ckeditor5.presets.default
48
50
  end
49
51
 
52
+ def presets
53
+ config.ckeditor5.presets
54
+ end
55
+
50
56
  def configure(&block)
51
57
  proxy = ConfigurationProxy.new(config.ckeditor5)
52
58
  proxy.instance_eval(&block)
@@ -57,6 +63,13 @@ module CKEditor5::Rails
57
63
 
58
64
  base.presets[preset]
59
65
  end
66
+
67
+ def find_preset!(preset)
68
+ found_preset = find_preset(preset)
69
+ return found_preset if found_preset.present?
70
+
71
+ raise PresetNotFoundError, "Preset '#{preset}' not found. Please define it in the initializer."
72
+ end
60
73
  end
61
74
 
62
75
  class ConfigurationProxy
@@ -8,6 +8,8 @@ module CKEditor5::Rails::Presets
8
8
  class Manager
9
9
  attr_reader :presets
10
10
 
11
+ alias to_h presets
12
+
11
13
  # Initializes a new Manager instance and sets up the default preset
12
14
  def initialize
13
15
  @presets = {}
@@ -86,7 +86,7 @@ module CKEditor5::Rails
86
86
  # Merge preset with configuration hash
87
87
  # @param overrides [Hash] Configuration options to merge
88
88
  # @return [self]
89
- def merge_with_hash!(**overrides) # rubocop:disable Metrics/AbcSize
89
+ def merge_with_hash!(**overrides)
90
90
  @version = Semver.new(overrides[:version]) if overrides.key?(:version)
91
91
  @premium = overrides.fetch(:premium, premium)
92
92
  @cdn = overrides.fetch(:cdn, cdn)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module CKEditor5
4
4
  module Rails
5
- VERSION = '1.20.1'
5
+ VERSION = '1.21.0'
6
6
 
7
7
  DEFAULT_CKEDITOR_VERSION = '44.0.0'
8
8
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'e2e/spec_helper'
4
+
5
+ RSpec.describe 'AJAX Form Integration', type: :feature, js: true do
6
+ before do
7
+ visit('form_ajax')
8
+ setup_form_tracking(page)
9
+ end
10
+
11
+ shared_examples 'an ajax form with CKEditor' do |form_testid, editor_testid, submit_testid, response_id| # rubocop:disable Metrics/BlockLength
12
+ let(:form) { find("[data-testid='#{form_testid}']") }
13
+ let(:editor) { find("[data-testid='#{editor_testid}']") }
14
+ let(:editable) { editor.find('.ck-editor__editable') }
15
+ let(:text_field) { editor.find('textarea', visible: :hidden) }
16
+ let(:submit_button) { find("[data-testid='#{submit_testid}']") }
17
+ let(:response_container) { find("##{response_id}") }
18
+
19
+ before do
20
+ expect(page).to have_css('.ck-editor__editable', wait: 10)
21
+ end
22
+
23
+ it 'loads editor properly' do
24
+ expect(page).to have_css("[data-testid='#{editor_testid}'] .ck-editor__editable")
25
+ expect(editor).to have_invisible_textarea
26
+ end
27
+
28
+ it 'validates required fields' do
29
+ editable.click
30
+ editable.send_keys([[:control, 'a'], :backspace])
31
+ text_field.set('')
32
+ submit_button.click
33
+
34
+ expect(form).not_to have_been_submitted
35
+ expect(text_field).to be_invalid
36
+ end
37
+
38
+ it 'submits form and shows response' do
39
+ test_content = "Test content #{Time.now.to_i}"
40
+
41
+ editable.click
42
+ editable.send_keys([[:control, 'a'], :backspace])
43
+ editable.send_keys(test_content)
44
+
45
+ sleep 1
46
+
47
+ submit_button.click
48
+
49
+ eventually(timeout: 13) do
50
+ expect(response_container).to be_visible
51
+ expect(response_container).to have_text('Success!')
52
+ expect(response_container).to have_text(test_content)
53
+
54
+ # Verify that CKEditor initializes in the response
55
+ response_editor = response_container.find('.ck-editor__editable', wait: 10)
56
+
57
+ expect(response_editor).to be_visible
58
+ expect(response_editor).to have_text(test_content)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe 'Regular AJAX form' do
64
+ it_behaves_like 'an ajax form with CKEditor',
65
+ 'rails-form',
66
+ 'rails-form-editor',
67
+ 'rails-form-submit',
68
+ 'response'
69
+ end
70
+
71
+ describe 'Turbo Stream form' do
72
+ it_behaves_like 'an ajax form with CKEditor',
73
+ 'rails-form-stream',
74
+ 'rails-form-editor-stream',
75
+ 'rails-form-submit-stream',
76
+ 'response-stream'
77
+ end
78
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'e2e/spec_helper'
4
+
5
+ RSpec.describe 'Lazy Assets', type: :feature do
6
+ context 'without JavaScript', js: false do
7
+ before do
8
+ visit 'classic_lazy_assets?no_embed=true'
9
+ end
10
+
11
+ it 'does not load CKEditor assets' do
12
+ expect(page).not_to have_css('link[href*="ckeditor5"]', visible: false)
13
+ expect(page).not_to have_css('script[src*="ckeditor5"]', visible: false)
14
+
15
+ scripts = page.all('script:not([type="importmap"]):not([type="module"])', visible: false)
16
+ external_scripts = scripts.reject { |s| s[:src].nil? }
17
+ expect(external_scripts).not_to include(match(/ckeditor5/))
18
+
19
+ expect(page).to have_css('script[type="importmap"]', visible: false)
20
+ end
21
+ end
22
+
23
+ context 'with JavaScript', js: true do
24
+ before { visit 'classic_lazy_assets' }
25
+
26
+ it 'loads editor when needed' do
27
+ expect(page).to have_css('.ck-editor__editable', wait: 10)
28
+ expect(page).to have_css('link[href*="ckeditor5"]', visible: false)
29
+ end
30
+
31
+ it 'initializes editor properly' do
32
+ editor = find('.ck-editor__editable')
33
+ expect(editor).to be_visible
34
+
35
+ editor.click
36
+ editor.send_keys('Test content')
37
+
38
+ expect(editor).to have_text('Test content')
39
+ end
40
+
41
+ it 'supports multiple editor instances' do
42
+ visit 'classic_lazy_assets?multiple=true'
43
+
44
+ editors = all('.ck-editor__editable', wait: 10)
45
+ expect(editors.length).to eq(2)
46
+
47
+ editors.each do |editor|
48
+ editor.click
49
+ editor.send_keys('Content for editor')
50
+ expect(editor).to have_text('Content for editor')
51
+ end
52
+ end
53
+ end
54
+ end
@@ -6,7 +6,10 @@ module FormHelpers
6
6
  window.lastSubmittedForm = null;
7
7
 
8
8
  document.addEventListener('submit', (e) => {
9
- e.preventDefault();
9
+ if (!e.target.hasAttribute('data-turbo-frame')) {
10
+ e.preventDefault();
11
+ }
12
+
10
13
  window.lastSubmittedForm = e.target.id;
11
14
  });
12
15
  JS
@@ -138,6 +138,59 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer do
138
138
  nonce: 'true'
139
139
  })
140
140
  end
141
+
142
+ context 'with lazy loading' do
143
+ subject(:html) { described_class.new(bundle, lazy: true).to_html }
144
+
145
+ it 'does not include preload tags' do
146
+ expect(html).not_to have_tag('link', with: { rel: 'preload' })
147
+ expect(html).not_to have_tag('link', with: { rel: 'modulepreload' })
148
+ end
149
+
150
+ it 'does not include stylesheet links' do
151
+ stylesheets.each do |url|
152
+ expect(html).not_to have_tag('link', with: { href: url, rel: 'stylesheet' })
153
+ end
154
+ end
155
+
156
+ it 'does not include window script tags' do
157
+ scripts.each do |script|
158
+ expect(html).not_to have_tag('script', with: { src: script.url }) if script.window?
159
+ end
160
+ end
161
+
162
+ it 'includes web component script' do
163
+ expect(html).to have_tag('script', with: { type: 'module' })
164
+ end
165
+ end
166
+
167
+ context 'with eager loading (lazy: false)' do
168
+ subject(:html) { described_class.new(bundle, lazy: false).to_html }
169
+
170
+ it 'includes preload tags' do
171
+ scripts.each do |script|
172
+ expect(html).to have_tag('link', with: {
173
+ href: script.url,
174
+ rel: script.esm? ? 'modulepreload' : 'preload'
175
+ })
176
+ end
177
+ end
178
+
179
+ it 'includes stylesheet links' do
180
+ stylesheets.each do |url|
181
+ expect(html).to have_tag('link', with: {
182
+ href: url,
183
+ rel: 'stylesheet'
184
+ })
185
+ end
186
+ end
187
+
188
+ it 'includes window script tags' do
189
+ scripts.each do |script|
190
+ expect(html).to have_tag('script', with: { src: script.url }) if script.window?
191
+ end
192
+ end
193
+ end
141
194
  end
142
195
 
143
196
  describe '.url_resource_preload_type' do
@@ -115,7 +115,8 @@ RSpec.describe CKEditor5::Rails::Assets::JSUrlImportMeta do
115
115
  expect(meta.to_h).to eq({
116
116
  url: url,
117
117
  import_name: 'module',
118
- import_as: 'alias'
118
+ import_as: 'alias',
119
+ translation: false
119
120
  })
120
121
  end
121
122
  end
@@ -35,10 +35,15 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
35
35
  end
36
36
 
37
37
  before do
38
- allow(CKEditor5::Rails::Engine).to receive(:find_preset).and_return(preset)
38
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset!).and_return(preset)
39
39
  allow(CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer).to receive(:new).and_return(serializer)
40
40
  end
41
41
 
42
+ after do
43
+ RSpec::Mocks.space.proxy_for(CKEditor5::Rails::Engine).reset
44
+ RSpec::Mocks.space.proxy_for(CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer).reset
45
+ end
46
+
42
47
  describe '#ckeditor5_assets' do
43
48
  context 'with valid preset' do
44
49
  it 'creates base bundle' do
@@ -206,12 +211,13 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
206
211
 
207
212
  context 'destructure non-matching preset override' do
208
213
  before do
209
- allow(CKEditor5::Rails::Engine).to receive(:find_preset).and_return(nil)
214
+ RSpec::Mocks.space.proxy_for(CKEditor5::Rails::Engine).reset
210
215
  end
211
216
 
212
217
  it 'raises error' do
213
218
  expect { helper.ckeditor5_assets(preset: :invalid) }
214
- .to raise_error(ArgumentError, /forgot to define your invalid preset/)
219
+ .to raise_error(CKEditor5::Rails::PresetNotFoundError)
220
+ RSpec::Mocks.space.proxy_for(CKEditor5::Rails::Engine).reset
215
221
  end
216
222
  end
217
223
 
@@ -256,6 +262,92 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
256
262
  end
257
263
  end
258
264
 
265
+ describe '#ckeditor5_lazy_javascript_tags' do
266
+ let(:web_component_html) do
267
+ '<script type="module" src="web-component.js">web component code</script>'.html_safe
268
+ end
269
+
270
+ let(:import_map_html) { '<script type="importmap">{"imports":{}}</script>'.html_safe }
271
+
272
+ let(:web_component_bundle) do
273
+ instance_double(CKEditor5::Rails::Assets::WebComponentBundle, to_html: web_component_html)
274
+ end
275
+ let(:import_map_bundle) do
276
+ instance_double(CKEditor5::Rails::Assets::AssetsImportMap, to_html: import_map_html)
277
+ end
278
+ let(:preset_manager) { instance_double(CKEditor5::Rails::Presets::Manager) }
279
+ let(:test_preset1) { instance_double(CKEditor5::Rails::Presets::PresetBuilder) }
280
+ let(:test_preset2) { instance_double(CKEditor5::Rails::Presets::PresetBuilder) }
281
+
282
+ before do
283
+ allow(CKEditor5::Rails::Assets::WebComponentBundle).to receive(:instance).and_return(
284
+ web_component_bundle
285
+ )
286
+
287
+ allow(CKEditor5::Rails::Assets::AssetsImportMap).to receive(:new).and_return(
288
+ import_map_bundle
289
+ )
290
+
291
+ allow(CKEditor5::Rails::Engine).to receive(:presets).and_return(preset_manager)
292
+ allow(preset_manager).to receive(:to_h).and_return({
293
+ test1: test_preset1,
294
+ test2: test_preset2
295
+ })
296
+
297
+ allow(helper).to receive(:create_preset_bundle).with(test_preset1)
298
+ .and_return(CKEditor5::Rails::Assets::AssetsBundle.new(
299
+ scripts: ['test1.js']
300
+ ))
301
+
302
+ allow(helper).to receive(:create_preset_bundle).with(test_preset2)
303
+ .and_return(CKEditor5::Rails::Assets::AssetsBundle.new(
304
+ scripts: ['test2.js']
305
+ ))
306
+ end
307
+
308
+ context 'when importmap is available' do
309
+ before do
310
+ allow(helper).to receive(:importmap_available?).and_return(true)
311
+ allow(helper).to receive(:importmap_rendered?).and_return(false)
312
+ end
313
+
314
+ it 'stores bundle in context and returns web component script' do
315
+ result = helper.ckeditor5_lazy_javascript_tags
316
+
317
+ expect(result).to have_tag('script', with: {
318
+ type: 'module',
319
+ src: 'web-component.js'
320
+ })
321
+ expect(context[:bundle].scripts).to match_array(['test1.js', 'test2.js'])
322
+ end
323
+
324
+ it 'raises error when importmap is already rendered' do
325
+ allow(helper).to receive(:importmap_rendered?).and_return(true)
326
+
327
+ expect { helper.ckeditor5_lazy_javascript_tags }
328
+ .to raise_error(CKEditor5::Rails::Cdn::Helpers::ImportmapAlreadyRenderedError)
329
+ end
330
+ end
331
+
332
+ context 'when importmap is not available' do
333
+ before do
334
+ allow(helper).to receive(:importmap_available?).and_return(false)
335
+ end
336
+
337
+ it 'returns both importmap and web component scripts as one string' do
338
+ result = helper.ckeditor5_lazy_javascript_tags
339
+
340
+ expect(result).to have_tag('script', with: { type: 'importmap' },
341
+ text: '{"imports":{}}')
342
+
343
+ expect(result).to have_tag('script', with: {
344
+ type: 'module',
345
+ src: 'web-component.js'
346
+ })
347
+ end
348
+ end
349
+ end
350
+
259
351
  describe 'cdn helper methods' do
260
352
  it 'generates helper methods for third-party CDNs' do
261
353
  expect(helper).to respond_to(:ckeditor5_unpkg_assets)
@@ -16,30 +16,69 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Editor do
16
16
  let(:context) { { preset: :default, cdn: :jsdelivr } }
17
17
 
18
18
  before do
19
+ RSpec::Mocks.space.proxy_for(CKEditor5::Rails::Engine).reset
20
+
19
21
  helper.instance_variable_set(:@__ckeditor_context, context)
22
+
20
23
  allow(preset).to receive(:type).and_return(:classic)
21
24
  allow(preset).to receive(:config).and_return({})
22
25
  allow(preset).to receive(:automatic_upgrades?).and_return(false)
26
+ allow(preset).to receive(:editable_height).and_return(nil)
23
27
  end
24
28
 
25
- describe '#ckeditor5_editor' do
29
+ before do
30
+ test_class.send(:public, :ckeditor5_context_or_fallback)
31
+ end
32
+
33
+ describe '#ckeditor5_context_or_fallback' do
26
34
  before do
27
- allow(helper).to receive(:find_preset).and_return(preset)
35
+ if helper.instance_variable_defined?(:@__ckeditor_context)
36
+ helper.remove_instance_variable(:@__ckeditor_context)
37
+ end
28
38
  end
29
39
 
30
- it 'raises error when context is not defined' do
31
- helper.remove_instance_variable(:@__ckeditor_context)
32
- expect { helper.ckeditor5_editor }.to raise_error(
33
- described_class::EditorContextError,
34
- /CKEditor installation context is not defined/
35
- )
40
+ it 'returns existing context when available' do
41
+ context = { preset: :custom, bundle: 'custom-bundle' }
42
+ helper.instance_variable_set(:@__ckeditor_context, context)
43
+
44
+ expect(helper.ckeditor5_context_or_fallback(nil)).to eq(context)
36
45
  end
37
46
 
38
- it 'raises error when preset is not found' do
39
- allow(helper).to receive(:find_preset).and_raise(described_class::PresetNotFoundError)
40
- expect do
41
- helper.ckeditor5_editor(preset: :unknown)
42
- end.to raise_error(described_class::PresetNotFoundError)
47
+ it 'creates context from preset when provided' do
48
+ custom_preset = instance_double(CKEditor5::Rails::Presets::PresetBuilder)
49
+
50
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset)
51
+ .with(:custom)
52
+ .and_return(custom_preset)
53
+
54
+ allow(helper).to receive(:create_preset_bundle)
55
+ .with(custom_preset)
56
+ .and_return('custom-bundle')
57
+
58
+ result = helper.ckeditor5_context_or_fallback(:custom)
59
+ expect(result).to match({
60
+ bundle: 'custom-bundle',
61
+ preset: custom_preset
62
+ })
63
+ end
64
+
65
+ it 'returns fallback context when no context or preset is available' do
66
+ RSpec::Mocks.space.proxy_for(CKEditor5::Rails::Engine).reset
67
+
68
+ allow(CKEditor5::Rails::Engine).to receive(:default_preset)
69
+ .and_return(:default)
70
+
71
+ result = helper.ckeditor5_context_or_fallback(nil)
72
+ expect(result).to match({
73
+ bundle: nil,
74
+ preset: :default
75
+ })
76
+ end
77
+ end
78
+
79
+ describe '#ckeditor5_editor' do
80
+ before do
81
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset).with(:default).and_return(preset)
43
82
  end
44
83
 
45
84
  it 'merges extra configuration with preset config' do
@@ -124,10 +163,6 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Editor do
124
163
  end
125
164
 
126
165
  context 'when using preset lookup' do
127
- before do
128
- RSpec::Mocks.space.proxy_for(helper).remove_stub(:find_preset)
129
- end
130
-
131
166
  it 'uses default preset when none specified' do
132
167
  expect(CKEditor5::Rails::Engine).to receive(:find_preset)
133
168
  .with(:default)
@@ -154,15 +189,40 @@ RSpec.describe CKEditor5::Rails::Editor::Helpers::Editor do
154
189
  helper.ckeditor5_editor(preset: :explicit)
155
190
  end
156
191
 
157
- it 'raises error when preset cannot be found' do
158
- allow(CKEditor5::Rails::Engine).to receive(:find_preset)
159
- .with(:unknown)
160
- .and_return(nil)
192
+ it 'raises error when preset is not found' do
193
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset).with(:unknown).and_return(nil)
194
+ expect do
195
+ helper.ckeditor5_editor(preset: :unknown)
196
+ end.to raise_error(CKEditor5::Rails::PresetNotFoundError)
197
+ end
198
+ end
199
+
200
+ it 'uses context from ckeditor5_context_or_fallback' do
201
+ custom_context = { preset: :custom, bundle: 'custom-bundle' }
202
+ allow(helper).to receive(:ckeditor5_context_or_fallback)
203
+ .with(nil)
204
+ .and_return(custom_context)
205
+
206
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset)
207
+ .with(:custom)
208
+ .and_return(preset)
209
+
210
+ helper.ckeditor5_editor
211
+ end
212
+
213
+ context 'when using editable height' do
214
+ it 'uses editable height from preset when not explicitly provided' do
215
+ allow(preset).to receive(:editable_height).and_return(400)
216
+
217
+ expect(helper.ckeditor5_editor).to include('editable-height="400px"')
218
+ end
219
+
220
+ it 'uses editable height from options when provided' do
221
+ expect(helper.ckeditor5_editor(editable_height: 500)).to include('editable-height="500px"')
222
+
223
+ allow(preset).to receive(:editable_height).and_return(700)
161
224
 
162
- expect { helper.ckeditor5_editor(preset: :unknown) }.to raise_error(
163
- described_class::PresetNotFoundError,
164
- 'Preset unknown is not defined.'
165
- )
225
+ expect(helper.ckeditor5_editor(editable_height: 600)).to include('editable-height="600px"')
166
226
  end
167
227
  end
168
228
  end