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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +99 -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/presets/toolbar_builder.rb +117 -3
  16. data/lib/ckeditor5/rails/version.rb +1 -1
  17. data/spec/e2e/features/ajax_form_integration_spec.rb +78 -0
  18. data/spec/e2e/features/lazy_assets_spec.rb +54 -0
  19. data/spec/e2e/support/form_helpers.rb +4 -1
  20. data/spec/lib/ckeditor5/rails/assets/assets_bundle_hml_serializer_spec.rb +53 -0
  21. data/spec/lib/ckeditor5/rails/assets/assets_bundle_spec.rb +2 -1
  22. data/spec/lib/ckeditor5/rails/cdn/helpers_spec.rb +95 -3
  23. data/spec/lib/ckeditor5/rails/editor/helpers/editor_helpers_spec.rb +85 -25
  24. data/spec/lib/ckeditor5/rails/editor/props_spec.rb +14 -34
  25. data/spec/lib/ckeditor5/rails/engine_spec.rb +10 -0
  26. data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +15 -2
  27. data/spec/lib/ckeditor5/rails/plugins/wproofreader_spec.rb +2 -0
  28. data/spec/lib/ckeditor5/rails/presets/toolbar_builder_spec.rb +119 -0
  29. data/spec/lib/ckeditor5/rails/version_detector_spec.rb +1 -1
  30. metadata +7 -2
@@ -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
@@ -3,34 +3,29 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe CKEditor5::Rails::Editor::Props do
6
- let(:controller_context) do
7
- {
8
- bundle: double('Bundle', translations_scripts: [{ path: 'translations/en.js' }]),
9
- license_key: nil
10
- }
11
- end
6
+ let(:bundle) { CKEditor5::Rails::Assets::AssetsBundle.new }
12
7
  let(:type) { :classic }
13
8
  let(:config) { { plugins: [], toolbar: { items: [] } } }
14
9
 
15
10
  describe '#initialize' do
16
11
  it 'accepts valid editor type' do
17
- expect { described_class.new(controller_context, :classic, {}) }.not_to raise_error
12
+ expect { described_class.new(type, {}, bundle: bundle) }.not_to raise_error
18
13
  end
19
14
 
20
15
  it 'raises error for invalid editor type' do
21
- expect { described_class.new(controller_context, :invalid, {}) }
16
+ expect { described_class.new(:invalid, {}, bundle: bundle) }
22
17
  .to raise_error(ArgumentError, 'Invalid editor type: invalid')
23
18
  end
24
19
  end
25
20
 
26
21
  describe '#to_attributes' do
27
- subject(:props) { described_class.new(controller_context, type, config) }
22
+ subject(:props) { described_class.new(type, config, bundle: bundle) }
28
23
 
29
24
  it 'includes required attributes' do
30
25
  attributes = props.to_attributes
31
26
  expect(attributes).to include(
32
27
  type: 'ClassicEditor',
33
- translations: String,
28
+ bundle: String,
34
29
  plugins: String,
35
30
  config: String,
36
31
  watchdog: true
@@ -38,33 +33,18 @@ RSpec.describe CKEditor5::Rails::Editor::Props do
38
33
  end
39
34
 
40
35
  context 'with editable height' do
41
- subject(:props) { described_class.new(controller_context, type, config, editable_height: '500px') }
36
+ subject(:props) { described_class.new(type, config, bundle: bundle, editable_height: '500px') }
42
37
 
43
38
  it 'includes editable-height attribute' do
44
39
  expect(props.to_attributes['editable-height']).to eq('500px')
45
40
  end
46
41
  end
47
42
 
48
- context 'with language' do
49
- subject(:props) { described_class.new(controller_context, type, config, language: 'pl') }
50
-
51
- it 'includes language in config' do
52
- config_json = props.to_attributes[:config]
53
-
54
- expect(config_json).to include('language')
55
- expect(JSON.parse(config_json)['language']).to eq({ 'ui' => 'pl' })
56
- end
57
- end
58
-
59
- context 'with license key' do
60
- let(:controller_context) do
61
- { bundle: double('Bundle', translations_scripts: []), license_key: 'ABC123' }
62
- end
43
+ context 'with watchdog disabled' do
44
+ subject(:props) { described_class.new(type, config, bundle: bundle, watchdog: false) }
63
45
 
64
- it 'includes license key in config' do
65
- config_json = props.to_attributes[:config]
66
- expect(config_json).to include('licenseKey')
67
- expect(JSON.parse(config_json)['licenseKey']).to eq('ABC123')
46
+ it 'includes watchdog: false in attributes' do
47
+ expect(props.to_attributes[:watchdog]).to be false
68
48
  end
69
49
  end
70
50
  end
@@ -87,7 +67,7 @@ RSpec.describe CKEditor5::Rails::Editor::Props do
87
67
 
88
68
  it 'raises error when editable height is set' do
89
69
  expect do
90
- described_class.new(controller_context, type, config, editable_height: '500px')
70
+ described_class.new(type, config, bundle: bundle, editable_height: '500px')
91
71
  end.to raise_error(CKEditor5::Rails::Editor::InvalidEditableHeightError)
92
72
  end
93
73
  end
@@ -96,18 +76,18 @@ RSpec.describe CKEditor5::Rails::Editor::Props do
96
76
  let(:type) { :classic }
97
77
 
98
78
  it 'accepts integer values' do
99
- props = described_class.new(controller_context, type, config, editable_height: 500)
79
+ props = described_class.new(type, config, bundle: bundle, editable_height: 500)
100
80
  expect(props.to_attributes['editable-height']).to eq('500px')
101
81
  end
102
82
 
103
83
  it 'accepts pixel string values' do
104
- props = described_class.new(controller_context, type, config, editable_height: '500px')
84
+ props = described_class.new(type, config, bundle: bundle, editable_height: '500px')
105
85
  expect(props.to_attributes['editable-height']).to eq('500px')
106
86
  end
107
87
 
108
88
  it 'raises error for invalid values' do
109
89
  expect do
110
- described_class.new(controller_context, type, config, editable_height: '500em')
90
+ described_class.new(type, config, bundle: bundle, editable_height: '500em')
111
91
  end.to raise_error(CKEditor5::Rails::Editor::InvalidEditableHeightError)
112
92
  end
113
93
  end
@@ -61,6 +61,16 @@ RSpec.describe CKEditor5::Rails::Engine do
61
61
  expect(described_class.find_preset(:custom)).to eq(preset)
62
62
  end
63
63
  end
64
+
65
+ describe '.presets' do
66
+ before do
67
+ RSpec::Mocks.space.proxy_for(described_class).reset
68
+ end
69
+
70
+ it 'returns presets from configuration' do
71
+ expect(described_class.presets).to eq(described_class.base.presets)
72
+ end
73
+ end
64
74
  end
65
75
 
66
76
  describe 'initializers' do
@@ -11,18 +11,31 @@ RSpec.describe CKEditor5::Rails::Hooks::Form do
11
11
  end
12
12
 
13
13
  before do
14
- template.ckeditor5_assets(version: '34.1.0')
14
+ template.ckeditor5_assets(version: '34.1.0', automatic_upgrades: false, cdn: :jsdelivr)
15
15
  end
16
16
 
17
17
  describe '#build_editor' do
18
18
  subject(:rendered_editor) { builder.build_editor(:content) }
19
19
 
20
20
  it 'renders ckeditor element' do
21
+ bundle_json = {
22
+ scripts: [
23
+ {
24
+ import_name: 'ckeditor5',
25
+ url: 'https://cdn.jsdelivr.net/npm/ckeditor5@34.1.0/dist/browser/ckeditor5.js',
26
+ translation: false
27
+ }
28
+ ],
29
+ stylesheets: [
30
+ 'https://cdn.jsdelivr.net/npm/ckeditor5@34.1.0/dist/browser/ckeditor5.css'
31
+ ]
32
+ }.to_json
33
+
21
34
  attrs = {
22
35
  name: 'post[content]',
23
36
  id: 'post_content',
24
37
  type: 'ClassicEditor',
25
- translations: '[]',
38
+ bundle: bundle_json,
26
39
  watchdog: 'true'
27
40
  }
28
41
 
@@ -24,6 +24,7 @@ RSpec.describe CKEditor5::Rails::Plugins::WProofreader do
24
24
  expected_hash = {
25
25
  type: :external,
26
26
  stylesheets: ["#{default_cdn}@#{default_version}/dist/browser/index.css"],
27
+ translation: false,
27
28
  url: "#{default_cdn}@#{default_version}/dist/browser/index.js",
28
29
  import_name: "#{default_cdn}@#{default_version}/dist/browser/index.js",
29
30
  import_as: 'WProofreader'
@@ -47,6 +48,7 @@ RSpec.describe CKEditor5::Rails::Plugins::WProofreader do
47
48
  expected_hash = {
48
49
  type: :external,
49
50
  stylesheets: ["#{custom_cdn}@#{custom_version}/dist/browser/index.css"],
51
+ translation: false,
50
52
  url: "#{custom_cdn}@#{custom_version}/dist/browser/index.js",
51
53
  import_name: "#{custom_cdn}@#{custom_version}/dist/browser/index.js",
52
54
  import_as: 'WProofreader'
@@ -67,4 +67,123 @@ RSpec.describe CKEditor5::Rails::Presets::ToolbarBuilder do
67
67
  end
68
68
  end
69
69
  end
70
+
71
+ describe '#break_line' do
72
+ it 'returns line break symbol' do
73
+ expect(builder.break_line).to eq(:-)
74
+ end
75
+ end
76
+
77
+ describe '#separator' do
78
+ it 'returns separator symbol' do
79
+ expect(builder.separator).to eq(:|)
80
+ end
81
+ end
82
+
83
+ describe '#group' do
84
+ it 'creates and adds a new group' do
85
+ builder.group(:text, label: 'Text') do
86
+ append(:bold, :italic)
87
+ end
88
+
89
+ group = builder.items.last
90
+ expect(group).to be_a(CKEditor5::Rails::Presets::ToolbarGroupItem)
91
+ expect(group.name).to eq(:text)
92
+ expect(group.items).to eq(%i[bold italic])
93
+ expect(group.label).to eq('Text')
94
+ end
95
+
96
+ it 'creates group without configuration block' do
97
+ group = builder.group(:text, label: 'Text')
98
+
99
+ expect(group).to be_a(CKEditor5::Rails::Presets::ToolbarGroupItem)
100
+ expect(group.items).to be_empty
101
+ end
102
+ end
103
+
104
+ describe '#find_group' do
105
+ it 'returns group by name' do
106
+ builder.group(:text, label: 'Text')
107
+ builder.group(:formatting, label: 'Format')
108
+
109
+ group = builder.find_group(:formatting)
110
+ expect(group).to be_a(CKEditor5::Rails::Presets::ToolbarGroupItem)
111
+ expect(group.name).to eq(:formatting)
112
+ end
113
+
114
+ it 'returns nil when group not found' do
115
+ expect(builder.find_group(:nonexistent)).to be_nil
116
+ end
117
+ end
118
+
119
+ describe '#remove_group' do
120
+ it 'removes group by name' do
121
+ builder.group(:text, label: 'Text')
122
+ builder.group(:formatting, label: 'Format')
123
+
124
+ builder.remove_group(:text)
125
+ expect(builder.find_group(:text)).to be_nil
126
+ expect(builder.find_group(:formatting)).to be_present
127
+ end
128
+
129
+ it 'ignores non-existent groups' do
130
+ builder.group(:text, label: 'Text')
131
+
132
+ expect { builder.remove_group(:nonexistent) }.not_to(change { builder.items.count })
133
+ end
134
+ end
135
+
136
+ describe 'interacting with groups' do
137
+ let(:text_group) do
138
+ builder.group(:text, label: 'Text') do
139
+ append(:bold, :italic)
140
+ end
141
+ end
142
+
143
+ before do
144
+ text_group
145
+ end
146
+
147
+ it 'prepends items before group' do
148
+ builder.prepend(:undo, :redo, before: :text)
149
+ expect(builder.items.map { |i| i.is_a?(CKEditor5::Rails::Presets::ToolbarGroupItem) ? i.name : i })
150
+ .to eq(%i[bold italic | link undo redo text])
151
+ end
152
+
153
+ it 'appends items after group' do
154
+ builder.append(:undo, :redo, after: :text)
155
+ expect(builder.items.map { |i| i.is_a?(CKEditor5::Rails::Presets::ToolbarGroupItem) ? i.name : i })
156
+ .to eq(%i[bold italic | link text undo redo])
157
+ end
158
+
159
+ it 'removes group using remove method' do
160
+ builder.remove(:text)
161
+ expect(builder.items).to eq(%i[bold italic | link])
162
+ end
163
+ end
164
+ end
165
+
166
+ RSpec.describe CKEditor5::Rails::Presets::ToolbarGroupItem do
167
+ let(:items) { %i[bold italic] }
168
+ let(:group) { described_class.new(:formatting, items, label: 'Format', icon: 'format') }
169
+
170
+ describe '#initialize' do
171
+ it 'creates a group with given parameters' do
172
+ expect(group.name).to eq(:formatting)
173
+ expect(group.items).to eq(items)
174
+ expect(group.label).to eq('Format')
175
+ expect(group.icon).to eq('format')
176
+ end
177
+ end
178
+
179
+ it 'inherits toolbar manipulation methods' do
180
+ group.append(:underline)
181
+ expect(group.items).to eq(%i[bold italic underline])
182
+
183
+ group.prepend(:heading)
184
+ expect(group.items).to eq(%i[heading bold italic underline])
185
+
186
+ group.remove(:italic)
187
+ expect(group.items).to eq(%i[heading bold underline])
188
+ end
70
189
  end
@@ -118,7 +118,7 @@ RSpec.describe CKEditor5::Rails::VersionDetector do
118
118
 
119
119
  private
120
120
 
121
- def setup_http_mock(response) # rubocop:disable Metrics/AbcSize
121
+ def setup_http_mock(response)
122
122
  allow(Net::HTTP).to receive(:new).and_return(
123
123
  double('Net::HTTP').tap do |http|
124
124
  allow(http).to receive(:use_ssl=)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ckeditor5
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.1
4
+ version: 1.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Bagiński
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-12-02 00:00:00.000000000 Z
12
+ date: 2024-12-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -53,6 +53,7 @@ files:
53
53
  - lib/ckeditor5/rails/assets/webcomponents/utils.mjs
54
54
  - lib/ckeditor5/rails/cdn/ckbox_bundle.rb
55
55
  - lib/ckeditor5/rails/cdn/ckeditor_bundle.rb
56
+ - lib/ckeditor5/rails/cdn/concerns/bundle_builder.rb
56
57
  - lib/ckeditor5/rails/cdn/helpers.rb
57
58
  - lib/ckeditor5/rails/cdn/url_generator.rb
58
59
  - lib/ckeditor5/rails/context/helpers.rb
@@ -83,9 +84,11 @@ files:
83
84
  - lib/ckeditor5/rails/semver.rb
84
85
  - lib/ckeditor5/rails/version.rb
85
86
  - lib/ckeditor5/rails/version_detector.rb
87
+ - spec/e2e/features/ajax_form_integration_spec.rb
86
88
  - spec/e2e/features/context_spec.rb
87
89
  - spec/e2e/features/editor_types_spec.rb
88
90
  - spec/e2e/features/form_integration_spec.rb
91
+ - spec/e2e/features/lazy_assets_spec.rb
89
92
  - spec/e2e/features/locale_spec.rb
90
93
  - spec/e2e/spec_helper.rb
91
94
  - spec/e2e/support/eventually.rb
@@ -149,9 +152,11 @@ signing_key:
149
152
  specification_version: 4
150
153
  summary: CKEditor 5 for Rails
151
154
  test_files:
155
+ - spec/e2e/features/ajax_form_integration_spec.rb
152
156
  - spec/e2e/features/context_spec.rb
153
157
  - spec/e2e/features/editor_types_spec.rb
154
158
  - spec/e2e/features/form_integration_spec.rb
159
+ - spec/e2e/features/lazy_assets_spec.rb
155
160
  - spec/e2e/features/locale_spec.rb
156
161
  - spec/e2e/spec_helper.rb
157
162
  - spec/e2e/support/eventually.rb