ckeditor5 1.20.1 → 1.22.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.
- 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)
|