ckeditor5 1.20.1 → 1.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +99 -2
- data/lib/ckeditor5/rails/assets/assets_bundle.rb +11 -1
- data/lib/ckeditor5/rails/assets/assets_bundle_html_serializer.rb +5 -10
- data/lib/ckeditor5/rails/assets/webcomponent_bundle.rb +10 -3
- data/lib/ckeditor5/rails/assets/webcomponents/components/editor.mjs +35 -2
- data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +36 -2
- data/lib/ckeditor5/rails/cdn/concerns/bundle_builder.rb +57 -0
- data/lib/ckeditor5/rails/cdn/helpers.rb +62 -55
- data/lib/ckeditor5/rails/editor/helpers/editor_helpers.rb +25 -19
- data/lib/ckeditor5/rails/editor/props.rb +7 -14
- data/lib/ckeditor5/rails/engine.rb +13 -0
- data/lib/ckeditor5/rails/presets/manager.rb +2 -0
- data/lib/ckeditor5/rails/presets/preset_builder.rb +1 -1
- data/lib/ckeditor5/rails/presets/toolbar_builder.rb +117 -3
- data/lib/ckeditor5/rails/version.rb +1 -1
- data/spec/e2e/features/ajax_form_integration_spec.rb +78 -0
- data/spec/e2e/features/lazy_assets_spec.rb +54 -0
- data/spec/e2e/support/form_helpers.rb +4 -1
- data/spec/lib/ckeditor5/rails/assets/assets_bundle_hml_serializer_spec.rb +53 -0
- data/spec/lib/ckeditor5/rails/assets/assets_bundle_spec.rb +2 -1
- data/spec/lib/ckeditor5/rails/cdn/helpers_spec.rb +95 -3
- data/spec/lib/ckeditor5/rails/editor/helpers/editor_helpers_spec.rb +85 -25
- data/spec/lib/ckeditor5/rails/editor/props_spec.rb +14 -34
- data/spec/lib/ckeditor5/rails/engine_spec.rb +10 -0
- data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +15 -2
- data/spec/lib/ckeditor5/rails/plugins/wproofreader_spec.rb +2 -0
- data/spec/lib/ckeditor5/rails/presets/toolbar_builder_spec.rb +119 -0
- data/spec/lib/ckeditor5/rails/version_detector_spec.rb +1 -1
- metadata +7 -2
@@ -14,16 +14,17 @@ module CKEditor5::Rails::Editor
|
|
14
14
|
}.freeze
|
15
15
|
|
16
16
|
def initialize(
|
17
|
-
|
18
|
-
|
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
|
-
@
|
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 :
|
44
|
+
attr_reader :bundle, :watchdog, :type, :config, :editable_height
|
44
45
|
|
45
46
|
def serialized_attributes
|
46
47
|
{
|
47
|
-
|
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
|
@@ -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)
|
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)
|
@@ -1,13 +1,46 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CKEditor5::Rails::Presets
|
4
|
+
# Builder class for configuring CKEditor5 toolbar items.
|
5
|
+
#
|
6
|
+
# @example Basic toolbar configuration
|
7
|
+
# toolbar = ToolbarBuilder.new([:bold, :italic])
|
8
|
+
# toolbar.append(:link)
|
9
|
+
# toolbar.prepend(:heading)
|
4
10
|
class ToolbarBuilder
|
5
11
|
attr_reader :items
|
6
12
|
|
13
|
+
# Initialize a new toolbar builder with given items.
|
14
|
+
#
|
15
|
+
# @param items [Array<Symbol>] Initial toolbar items
|
16
|
+
# @example Create new toolbar
|
17
|
+
# ToolbarBuilder.new([:bold, :italic, :|, :link])
|
7
18
|
def initialize(items)
|
8
19
|
@items = items
|
9
20
|
end
|
10
21
|
|
22
|
+
# Returns toolbar line break symbol
|
23
|
+
#
|
24
|
+
# @return [Symbol] Line break symbol (-)
|
25
|
+
# @example Add line break to toolbar
|
26
|
+
# toolbar do
|
27
|
+
# append :bold, break_line, :italic
|
28
|
+
# end
|
29
|
+
def break_line
|
30
|
+
:-
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns toolbar separator symbol
|
34
|
+
#
|
35
|
+
# @return [Symbol] Separator symbol (|)
|
36
|
+
# @example Add separator to toolbar
|
37
|
+
# toolbar do
|
38
|
+
# append :bold, separator, :italic
|
39
|
+
# end
|
40
|
+
def separator
|
41
|
+
:|
|
42
|
+
end
|
43
|
+
|
11
44
|
# Remove items from the editor toolbar.
|
12
45
|
#
|
13
46
|
# @param removed_items [Array<Symbol>] Toolbar items to be removed
|
@@ -16,7 +49,9 @@ module CKEditor5::Rails::Presets
|
|
16
49
|
# remove :underline, :heading
|
17
50
|
# end
|
18
51
|
def remove(*removed_items)
|
19
|
-
|
52
|
+
items.delete_if do |existing_item|
|
53
|
+
removed_items.any? { |item_to_remove| item_matches?(existing_item, item_to_remove) }
|
54
|
+
end
|
20
55
|
end
|
21
56
|
|
22
57
|
# Prepend items to the editor toolbar.
|
@@ -34,7 +69,7 @@ module CKEditor5::Rails::Presets
|
|
34
69
|
# @raise [ArgumentError] When the specified 'before' item is not found
|
35
70
|
def prepend(*prepended_items, before: nil)
|
36
71
|
if before
|
37
|
-
index =
|
72
|
+
index = find_item_index(before)
|
38
73
|
raise ArgumentError, "Item '#{before}' not found in array" unless index
|
39
74
|
|
40
75
|
items.insert(index, *prepended_items)
|
@@ -58,7 +93,7 @@ module CKEditor5::Rails::Presets
|
|
58
93
|
# @raise [ArgumentError] When the specified 'after' item is not found
|
59
94
|
def append(*appended_items, after: nil)
|
60
95
|
if after
|
61
|
-
index =
|
96
|
+
index = find_item_index(after)
|
62
97
|
raise ArgumentError, "Item '#{after}' not found in array" unless index
|
63
98
|
|
64
99
|
items.insert(index + 1, *appended_items)
|
@@ -66,5 +101,84 @@ module CKEditor5::Rails::Presets
|
|
66
101
|
items.push(*appended_items)
|
67
102
|
end
|
68
103
|
end
|
104
|
+
|
105
|
+
# Find group by name in toolbar items
|
106
|
+
#
|
107
|
+
# @param name [Symbol] Group name to find
|
108
|
+
# @return [ToolbarGroupItem, nil] Found group or nil
|
109
|
+
def find_group(name)
|
110
|
+
items.find { |item| item.is_a?(ToolbarGroupItem) && item.name == name }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Remove group by name from toolbar items
|
114
|
+
#
|
115
|
+
# @param name [Symbol] Group name to remove
|
116
|
+
def remove_group(name)
|
117
|
+
items.delete_if { |item| item.is_a?(ToolbarGroupItem) && item.name == name }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Create and add new group to toolbar
|
121
|
+
#
|
122
|
+
# @param name [Symbol] Group name
|
123
|
+
# @param options [Hash] Group options (label:, icons:)
|
124
|
+
# @param block [Proc] Configuration block
|
125
|
+
# @return [ToolbarGroupItem] Created group
|
126
|
+
def group(name, **options, &block)
|
127
|
+
group = ToolbarGroupItem.new(name, [], **options)
|
128
|
+
group.instance_eval(&block) if block_given?
|
129
|
+
items << group
|
130
|
+
group
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# Find index of an item or group by name
|
136
|
+
#
|
137
|
+
# @param item [Symbol] Item or group name to find
|
138
|
+
# @return [Integer, nil] Index of the found item or nil
|
139
|
+
def find_item_index(item)
|
140
|
+
items.find_index { |existing_item| item_matches?(existing_item, item) }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Checks if the existing item matches the given item or group name
|
144
|
+
#
|
145
|
+
# @param existing_item [Symbol, ToolbarGroupItem] Item to check
|
146
|
+
# @param item [Symbol] Item or group name to match against
|
147
|
+
# @return [Boolean] true if items match, false otherwise
|
148
|
+
# @example Check if items match
|
149
|
+
# item_matches?(:bold, :bold) # => true
|
150
|
+
# item_matches?(group(:text), :text) # => true
|
151
|
+
def item_matches?(existing_item, item)
|
152
|
+
if existing_item.is_a?(ToolbarGroupItem)
|
153
|
+
existing_item.name == item
|
154
|
+
else
|
155
|
+
existing_item == item
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Builder class for configuring CKEditor5 toolbar groups.
|
161
|
+
# Allows creating named groups of toolbar items with optional labels and icons.
|
162
|
+
#
|
163
|
+
# @example Creating a text formatting group
|
164
|
+
# group = ToolbarGroupItem.new(:text_formatting, [:bold, :italic], label: 'Text')
|
165
|
+
# group.append(:underline)
|
166
|
+
class ToolbarGroupItem < ToolbarBuilder
|
167
|
+
attr_reader :name, :label, :icon
|
168
|
+
|
169
|
+
# Initialize a new toolbar group item.
|
170
|
+
#
|
171
|
+
# @param name [Symbol] Name of the toolbar group
|
172
|
+
# @param items [Array<Symbol>, ToolbarBuilder] Items to be included in the group
|
173
|
+
# @param label [String, nil] Optional label for the group
|
174
|
+
# @param icon [String, nil] Optional icon for the group
|
175
|
+
# @example Create a new toolbar group
|
176
|
+
# ToolbarGroupItem.new(:text, [:bold, :italic], label: 'Text formatting')
|
177
|
+
def initialize(name, items = [], label: nil, icon: nil)
|
178
|
+
super(items)
|
179
|
+
@name = name
|
180
|
+
@label = label
|
181
|
+
@icon = icon
|
182
|
+
end
|
69
183
|
end
|
70
184
|
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.
|
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
|
@@ -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
|
-
|
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(
|
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)
|