ckeditor5 1.15.8 → 1.15.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -5
  3. data/README.md +3 -0
  4. data/lib/ckeditor5/rails/cdn/ckbox_bundle.rb +2 -2
  5. data/lib/ckeditor5/rails/cdn/helpers.rb +6 -0
  6. data/lib/ckeditor5/rails/context/helpers.rb +1 -1
  7. data/lib/ckeditor5/rails/editor/editable_height_normalizer.rb +50 -0
  8. data/lib/ckeditor5/rails/editor/helpers/config_helpers.rb +3 -3
  9. data/lib/ckeditor5/rails/editor/helpers/editor_helpers.rb +5 -4
  10. data/lib/ckeditor5/rails/editor/props.rb +3 -20
  11. data/lib/ckeditor5/rails/engine.rb +3 -2
  12. data/lib/ckeditor5/rails/plugins/simple_upload_adapter.rb +1 -1
  13. data/lib/ckeditor5/rails/presets/plugins_builder.rb +9 -9
  14. data/lib/ckeditor5/rails/presets/preset_builder.rb +6 -9
  15. data/lib/ckeditor5/rails/version.rb +1 -1
  16. data/lib/ckeditor5/rails/version_detector.rb +6 -0
  17. data/spec/lib/ckeditor5/rails/assets/asset_bundle_hml_serializer_spec.rb +111 -0
  18. data/spec/lib/ckeditor5/rails/assets/assets_bundle_spec.rb +191 -0
  19. data/spec/lib/ckeditor5/rails/cdn/ckbox_bundle_spec.rb +69 -0
  20. data/spec/lib/ckeditor5/rails/cdn/ckeditor_bundle_spec.rb +72 -0
  21. data/spec/lib/ckeditor5/rails/cdn/helpers_spec.rb +235 -0
  22. data/spec/lib/ckeditor5/rails/context/helpers_spec.rb +67 -0
  23. data/spec/lib/ckeditor5/rails/context/props_spec.rb +70 -0
  24. data/spec/lib/ckeditor5/rails/editor/editable_height_normalizer_spec.rb +50 -0
  25. data/spec/lib/ckeditor5/rails/editor/helpers/config_helpers_spec.rb +52 -0
  26. data/spec/lib/ckeditor5/rails/editor/helpers/editor_helpers_spec.rb +192 -0
  27. data/spec/lib/ckeditor5/rails/editor/props_inline_plugin_spec.rb +43 -0
  28. data/spec/lib/ckeditor5/rails/editor/props_plugin_spec.rb +66 -0
  29. data/spec/lib/ckeditor5/rails/editor/props_spec.rb +104 -0
  30. data/spec/lib/ckeditor5/rails/engine_spec.rb +88 -0
  31. data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +203 -0
  32. data/spec/lib/ckeditor5/rails/hooks/simple_form_spec.rb +100 -0
  33. data/spec/lib/ckeditor5/rails/presets/manager_spec.rb +100 -0
  34. data/spec/lib/ckeditor5/rails/presets/plugins_builder_spec.rb +98 -0
  35. data/spec/lib/ckeditor5/rails/presets/preset_builder_spec.rb +347 -0
  36. data/spec/lib/ckeditor5/rails/presets/toolbar_builder_spec.rb +70 -0
  37. data/spec/lib/ckeditor5/rails/semver_spec.rb +58 -0
  38. data/spec/lib/ckeditor5/rails/version_detector_spec.rb +131 -0
  39. data/spec/lib/ckeditor5/rails/version_spec.rb +25 -0
  40. data/spec/spec_helper.rb +8 -2
  41. data/spec/support/test_models.rb +6 -0
  42. metadata +49 -2
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Assets::AssetsBundle do
6
+ let(:concrete_class) do
7
+ Class.new(described_class) do
8
+ def scripts
9
+ []
10
+ end
11
+
12
+ def stylesheets
13
+ []
14
+ end
15
+ end
16
+ end
17
+
18
+ describe '#initialize' do
19
+ it 'raises error when required methods are not implemented' do
20
+ expect { described_class.new }.to raise_error(NotImplementedError)
21
+ end
22
+
23
+ it 'initializes successfully when required methods are implemented' do
24
+ expect { concrete_class.new }.not_to raise_error
25
+ end
26
+ end
27
+
28
+ describe '#empty?' do
29
+ subject(:bundle) { concrete_class.new }
30
+
31
+ it 'returns true when no assets are present' do
32
+ expect(bundle).to be_empty
33
+ end
34
+ end
35
+
36
+ describe '#translations_scripts' do
37
+ let(:bundle) { concrete_class.new }
38
+ let(:translation_script) { instance_double(CKEditor5::Rails::Assets::JSExportsMeta, translation?: true) }
39
+ let(:regular_script) { instance_double(CKEditor5::Rails::Assets::JSExportsMeta, translation?: false) }
40
+
41
+ before do
42
+ allow(bundle).to receive(:scripts).and_return([translation_script, regular_script])
43
+ end
44
+
45
+ it 'returns only translation scripts' do
46
+ expect(bundle.translations_scripts).to eq([translation_script])
47
+ end
48
+ end
49
+
50
+ describe '#<<' do
51
+ let(:script1) { instance_double(CKEditor5::Rails::Assets::JSExportsMeta) }
52
+ let(:script2) { instance_double(CKEditor5::Rails::Assets::JSExportsMeta) }
53
+ let(:stylesheet1) { '/path/to/style1.css' }
54
+ let(:stylesheet2) { '/path/to/style2.css' }
55
+
56
+ let(:bundle1) do
57
+ Class.new(described_class) do
58
+ attr_writer :scripts, :stylesheets
59
+
60
+ def scripts
61
+ @scripts ||= []
62
+ end
63
+
64
+ def stylesheets
65
+ @stylesheets ||= []
66
+ end
67
+ end.new
68
+ end
69
+
70
+ let(:bundle2) do
71
+ Class.new(described_class) do
72
+ attr_writer :scripts, :stylesheets
73
+
74
+ def scripts
75
+ @scripts ||= []
76
+ end
77
+
78
+ def stylesheets
79
+ @stylesheets ||= []
80
+ end
81
+ end.new
82
+ end
83
+
84
+ before do
85
+ bundle1.scripts = [script1]
86
+ bundle1.stylesheets = [stylesheet1]
87
+ bundle2.scripts = [script2]
88
+ bundle2.stylesheets = [stylesheet2]
89
+ end
90
+
91
+ it 'raises TypeError when argument is not an AssetsBundle' do
92
+ expect { bundle1 << 'not a bundle' }.to raise_error(TypeError)
93
+ end
94
+
95
+ it 'merges scripts and stylesheets from both bundles' do
96
+ bundle1 << bundle2
97
+
98
+ expect(bundle1.scripts).to eq([script1, script2])
99
+ expect(bundle1.stylesheets).to eq([stylesheet1, stylesheet2])
100
+ end
101
+ end
102
+
103
+ describe '#preloads' do
104
+ let(:script1) { instance_double(CKEditor5::Rails::Assets::JSExportsMeta, url: '/js/script1.js') }
105
+ let(:script2) { instance_double(CKEditor5::Rails::Assets::JSExportsMeta, url: '/js/script2.js') }
106
+ let(:stylesheet1) { '/css/style1.css' }
107
+ let(:stylesheet2) { '/css/style2.css' }
108
+
109
+ let(:bundle) do
110
+ Class.new(described_class) do
111
+ attr_writer :scripts, :stylesheets
112
+
113
+ def scripts
114
+ @scripts ||= []
115
+ end
116
+
117
+ def stylesheets
118
+ @stylesheets ||= []
119
+ end
120
+ end.new
121
+ end
122
+
123
+ before do
124
+ bundle.scripts = [script1, script2]
125
+ bundle.stylesheets = [stylesheet1, stylesheet2]
126
+ end
127
+
128
+ it 'returns array of stylesheet paths and script urls' do
129
+ expect(bundle.preloads).to eq([
130
+ '/css/style1.css',
131
+ '/css/style2.css',
132
+ '/js/script1.js',
133
+ '/js/script2.js'
134
+ ])
135
+ end
136
+ end
137
+ end
138
+
139
+ RSpec.describe CKEditor5::Rails::Assets::JSExportsMeta do
140
+ let(:url) { '/path/to/script.js' }
141
+
142
+ describe '#initialize' do
143
+ it 'creates instance with import_name' do
144
+ meta = described_class.new(url, import_name: 'module')
145
+ expect(meta.import_name).to eq('module')
146
+ end
147
+
148
+ it 'creates instance with window_name' do
149
+ meta = described_class.new(url, window_name: 'MyModule')
150
+ expect(meta.window_name).to eq('MyModule')
151
+ end
152
+
153
+ it 'marks as translation when specified' do
154
+ meta = described_class.new(url, translation: true, import_name: 'module')
155
+ expect(meta).to be_translation
156
+ end
157
+ end
158
+ end
159
+
160
+ RSpec.describe CKEditor5::Rails::Assets::JSImportMeta do
161
+ describe '#initialize' do
162
+ it 'raises error when neither import_name nor window_name is provided' do
163
+ expect { described_class.new }.to raise_error(ArgumentError)
164
+ end
165
+
166
+ it 'raises error when import_as is present without import_name' do
167
+ expect { described_class.new(import_as: 'alias', window_name: 'Module') }
168
+ .to raise_error(ArgumentError)
169
+ end
170
+
171
+ it 'creates valid instance with import_name' do
172
+ meta = described_class.new(import_name: 'module')
173
+ expect(meta).to be_esm
174
+ end
175
+
176
+ it 'creates valid instance with window_name' do
177
+ meta = described_class.new(window_name: 'Module')
178
+ expect(meta).to be_window
179
+ end
180
+ end
181
+
182
+ describe '#to_h' do
183
+ it 'returns hash with only present values' do
184
+ meta = described_class.new(import_name: 'module', import_as: 'alias')
185
+ expect(meta.to_h).to eq({
186
+ import_name: 'module',
187
+ import_as: 'alias'
188
+ })
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Cdn::CKBoxBundle do
6
+ let(:version) { CKEditor5::Rails::Semver.new('2.6.0') }
7
+ let(:cdn) { :jsdelivr }
8
+ let(:theme) { :lark }
9
+ let(:translations) { [] }
10
+ let(:bundle) { described_class.new(version, theme: theme, cdn: cdn, translations: translations) }
11
+
12
+ describe '#initialize' do
13
+ context 'with valid parameters' do
14
+ it 'creates bundle successfully' do
15
+ expect { bundle }.not_to raise_error
16
+ end
17
+ end
18
+
19
+ context 'with invalid parameters' do
20
+ it 'raises error for invalid version' do
21
+ expect { described_class.new('invalid', theme: theme) }
22
+ .to raise_error(ArgumentError, 'version must be semver')
23
+ end
24
+
25
+ it 'raises error for invalid theme type' do
26
+ expect { described_class.new(version, theme: 123) }
27
+ .to raise_error(ArgumentError, 'theme must be a string or symbol')
28
+ end
29
+
30
+ it 'raises error for invalid translations type' do
31
+ expect { described_class.new(version, theme: theme, translations: 'invalid') }
32
+ .to raise_error(ArgumentError, 'translations must be an array')
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#scripts' do
38
+ it 'returns array with main script' do
39
+ expect(bundle.scripts.first).to be_a(CKEditor5::Rails::Assets::JSExportsMeta)
40
+ expect(bundle.scripts.first.url).to include('ckbox.js')
41
+ expect(bundle.scripts.first.window_name).to eq('CKBox')
42
+ end
43
+
44
+ context 'with translations' do
45
+ let(:translations) { [:pl] }
46
+
47
+ it 'includes translation scripts' do
48
+ translation_script = bundle.scripts.last
49
+ expect(translation_script.url).to include('translations/pl.js')
50
+ expect(translation_script.window_name).to eq('CKBOX_TRANSLATIONS')
51
+ expect(translation_script.translation?).to be true
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '#stylesheets' do
57
+ it 'returns array with theme stylesheet' do
58
+ expect(bundle.stylesheets.first).to include('styles/themes/lark.css')
59
+ end
60
+
61
+ context 'with custom theme' do
62
+ let(:theme) { :custom }
63
+
64
+ it 'uses custom theme in stylesheet path' do
65
+ expect(bundle.stylesheets.first).to include('styles/themes/custom.css')
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Cdn::CKEditorBundle do
6
+ let(:version) { CKEditor5::Rails::Semver.new('34.1.0') }
7
+ let(:import_name) { '@ckeditor/ckeditor5-build-classic' }
8
+ let(:translations) { %w[pl de] }
9
+ let(:cdn) { 'https://cdn.example.com' }
10
+
11
+ before do
12
+ allow(CKEditor5::Rails::Engine.default_preset).to receive(:cdn).and_return(cdn)
13
+ allow_any_instance_of(described_class).to receive(:create_cdn_url) do |_, pkg, ver, file|
14
+ "#{cdn}/npm/#{pkg}@#{ver}/build/#{file}"
15
+ end
16
+ end
17
+
18
+ describe '#initialize' do
19
+ it 'creates instance with valid parameters' do
20
+ expect do
21
+ described_class.new(version, import_name, translations: translations)
22
+ end.not_to raise_error
23
+ end
24
+
25
+ it 'raises error when version is not Semver' do
26
+ expect do
27
+ described_class.new('34.1.0', import_name)
28
+ end.to raise_error(ArgumentError, 'version must be semver')
29
+ end
30
+
31
+ it 'raises error when import_name is not string' do
32
+ expect do
33
+ described_class.new(version, :invalid)
34
+ end.to raise_error(ArgumentError, 'import_name must be a string')
35
+ end
36
+
37
+ it 'raises error when translations is not array' do
38
+ expect do
39
+ described_class.new(version, import_name, translations: 'invalid')
40
+ end.to raise_error(ArgumentError, 'translations must be an array')
41
+ end
42
+ end
43
+
44
+ describe '#scripts' do
45
+ subject(:bundle) { described_class.new(version, import_name, translations: translations) }
46
+
47
+ it 'returns main script and translation scripts' do
48
+ expect(bundle.scripts.count).to eq(3)
49
+ expect(bundle.scripts.first.url).to eq("#{cdn}/npm/#{import_name}@#{version}/build/#{import_name}.js")
50
+ expect(bundle.scripts.first).not_to be_translation
51
+ end
52
+
53
+ it 'includes translation scripts' do
54
+ translation_scripts = bundle.scripts.select(&:translation?)
55
+ expect(translation_scripts.count).to eq(2)
56
+ expect(translation_scripts.map(&:url)).to contain_exactly(
57
+ "#{cdn}/npm/#{import_name}@#{version}/build/translations/pl.js",
58
+ "#{cdn}/npm/#{import_name}@#{version}/build/translations/de.js"
59
+ )
60
+ end
61
+ end
62
+
63
+ describe '#stylesheets' do
64
+ subject(:bundle) { described_class.new(version, import_name) }
65
+
66
+ it 'returns stylesheet URL' do
67
+ expect(bundle.stylesheets).to eq(
68
+ ["#{cdn}/npm/#{import_name}@#{version}/build/#{import_name}.css"]
69
+ )
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
6
+ let(:test_class) { Class.new { include CKEditor5::Rails::Cdn::Helpers } }
7
+ let(:helper) { test_class.new }
8
+ let(:preset) do
9
+ instance_double(
10
+ CKEditor5::Rails::Presets::PresetBuilder,
11
+ to_h_with_overrides: {
12
+ cdn: :cloud,
13
+ version: '34.1.0',
14
+ type: 'classic',
15
+ translations: %w[pl],
16
+ ckbox: nil,
17
+ license_key: nil,
18
+ premium: false
19
+ }
20
+ )
21
+ end
22
+ let(:bundle_html) { '<script src="test.js"></script>' }
23
+ let(:serializer) do
24
+ instance_double(CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer, to_html: bundle_html)
25
+ end
26
+
27
+ before do
28
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset).and_return(preset)
29
+ allow(CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer).to receive(:new).and_return(serializer)
30
+ end
31
+
32
+ describe '#ckeditor5_assets' do
33
+ context 'with valid preset' do
34
+ it 'returns serialized bundle html' do
35
+ expect(helper.ckeditor5_assets(preset: :default)).to eq(bundle_html)
36
+ end
37
+
38
+ it 'creates base bundle' do
39
+ expect(CKEditor5::Rails::Cdn::CKEditorBundle).to receive(:new)
40
+ .with(
41
+ instance_of(CKEditor5::Rails::Semver),
42
+ 'ckeditor5',
43
+ translations: %w[pl],
44
+ cdn: :cloud
45
+ )
46
+ .and_call_original
47
+
48
+ helper.ckeditor5_assets(preset: :default)
49
+ end
50
+
51
+ context 'with premium features' do
52
+ let(:preset) do
53
+ instance_double(
54
+ CKEditor5::Rails::Presets::PresetBuilder,
55
+ to_h_with_overrides: {
56
+ cdn: :cloud,
57
+ version: '34.1.0',
58
+ type: 'classic',
59
+ translations: %w[pl],
60
+ ckbox: nil,
61
+ license_key: nil,
62
+ premium: true
63
+ }
64
+ )
65
+ end
66
+
67
+ it 'creates base and premium bundles' do
68
+ expect(CKEditor5::Rails::Cdn::CKEditorBundle).to receive(:new)
69
+ .with(
70
+ instance_of(CKEditor5::Rails::Semver),
71
+ 'ckeditor5',
72
+ translations: %w[pl],
73
+ cdn: :cloud
74
+ )
75
+ .and_call_original
76
+ .ordered
77
+
78
+ expect(CKEditor5::Rails::Cdn::CKEditorBundle).to receive(:new)
79
+ .with(
80
+ instance_of(CKEditor5::Rails::Semver),
81
+ 'ckeditor5-premium-features',
82
+ translations: %w[pl],
83
+ cdn: :cloud
84
+ )
85
+ .and_call_original
86
+ .ordered
87
+
88
+ helper.ckeditor5_assets(preset: :default)
89
+ end
90
+ end
91
+
92
+ context 'with ckbox' do
93
+ let(:preset) do
94
+ instance_double(
95
+ CKEditor5::Rails::Presets::PresetBuilder,
96
+ to_h_with_overrides: {
97
+ cdn: :cloud,
98
+ version: '34.1.0',
99
+ type: 'classic',
100
+ translations: %w[pl],
101
+ ckbox: { version: '1.0.0', theme: :lark },
102
+ license_key: nil,
103
+ premium: false
104
+ }
105
+ )
106
+ end
107
+
108
+ it 'creates ckbox bundle' do
109
+ expect(CKEditor5::Rails::Cdn::CKBoxBundle).to receive(:new)
110
+ .with(
111
+ instance_of(CKEditor5::Rails::Semver),
112
+ theme: :lark,
113
+ cdn: :ckbox
114
+ )
115
+ .and_call_original
116
+
117
+ helper.ckeditor5_assets(preset: :default)
118
+ end
119
+ end
120
+
121
+ context 'when destructuring preset hash' do
122
+ let(:preset) do
123
+ instance_double(
124
+ CKEditor5::Rails::Presets::PresetBuilder,
125
+ to_h_with_overrides: {
126
+ cdn: :cloud,
127
+ version: '34.1.0',
128
+ type: 'classic',
129
+ translations: %w[pl],
130
+ ckbox: nil,
131
+ license_key: nil,
132
+ premium: false,
133
+ extra: 'value'
134
+ }
135
+ )
136
+ end
137
+
138
+ it 'successfully matches and extracts required parameters' do
139
+ expect { helper.ckeditor5_assets(preset: :default) }.not_to raise_error
140
+ end
141
+ end
142
+ end
143
+
144
+ context 'with invalid preset' do
145
+ before do
146
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset).and_return(nil)
147
+ end
148
+
149
+ it 'raises error' do
150
+ expect { helper.ckeditor5_assets(preset: :invalid) }
151
+ .to raise_error(ArgumentError, /forgot to define your invalid preset/)
152
+ end
153
+ end
154
+
155
+ context 'with missing required parameters' do
156
+ let(:preset) do
157
+ instance_double(
158
+ CKEditor5::Rails::Presets::PresetBuilder,
159
+ to_h_with_overrides: { cdn: :cloud }
160
+ )
161
+ end
162
+
163
+ before do
164
+ allow(helper).to receive(:merge_with_editor_preset).and_return({})
165
+ end
166
+
167
+ it 'raises error about missing required parameters' do
168
+ expect { helper.ckeditor5_assets(preset: :default) }
169
+ .to raise_error(NoMatchingPatternKeyError)
170
+ end
171
+ end
172
+
173
+ context 'with empty hash from preset' do
174
+ let(:preset) do
175
+ instance_double(
176
+ CKEditor5::Rails::Presets::PresetBuilder,
177
+ to_h_with_overrides: {}
178
+ )
179
+ end
180
+
181
+ it 'raises error about missing version and type' do
182
+ expect { helper.ckeditor5_assets(preset: :default) }
183
+ .to raise_error(ArgumentError, /forgot to define version/)
184
+ end
185
+ end
186
+ end
187
+
188
+ context 'when overriding preset values with kwargs' do
189
+ let(:preset) do
190
+ CKEditor5::Rails::Presets::PresetBuilder.new do
191
+ version '34.1.0'
192
+ type :classic
193
+ translations :pl
194
+ cdn :cloud
195
+ license_key 'preset-license'
196
+ premium false
197
+ end
198
+ end
199
+
200
+ before do
201
+ allow(CKEditor5::Rails::Engine).to receive(:find_preset).and_return(preset)
202
+ end
203
+
204
+ it 'allows overriding preset values with kwargs' do
205
+ result = helper.send(:merge_with_editor_preset, :default, license_key: 'overridden-license')
206
+ expect(result).to include(license_key: 'overridden-license')
207
+ end
208
+
209
+ it 'preserves non-overridden preset values' do
210
+ result = helper.send(:merge_with_editor_preset, :default, license_key: 'overridden-license')
211
+ expect(result).to eq(
212
+ version: '34.1.0',
213
+ premium: false,
214
+ cdn: :cloud,
215
+ translations: [:pl],
216
+ license_key: 'overridden-license',
217
+ type: :classic,
218
+ ckbox: nil,
219
+ config: { plugins: [], toolbar: [] }
220
+ )
221
+ end
222
+ end
223
+
224
+ describe 'cdn helper methods' do
225
+ it 'generates helper methods for third-party CDNs' do
226
+ expect(helper).to respond_to(:ckeditor5_unpkg_assets)
227
+ expect(helper).to respond_to(:ckeditor5_jsdelivr_assets)
228
+ end
229
+
230
+ it 'calls main helper with proper cdn parameter' do
231
+ expect(helper).to receive(:ckeditor5_assets).with(cdn: :unpkg, version: '34.1.0')
232
+ helper.ckeditor5_unpkg_assets(version: '34.1.0')
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'action_view'
5
+
6
+ RSpec.describe CKEditor5::Rails::Context::Helpers do
7
+ let(:test_class) do
8
+ Class.new do
9
+ include ActionView::Helpers::TagHelper
10
+ include CKEditor5::Rails::Context::Helpers
11
+ end
12
+ end
13
+
14
+ let(:helper) { test_class.new }
15
+
16
+ describe '#ckeditor5_context' do
17
+ it 'creates context component with default attributes' do
18
+ expect(helper.ckeditor5_context).to have_tag(
19
+ 'ckeditor-context-component',
20
+ with: {
21
+ plugins: '[]',
22
+ config: '{}'
23
+ }
24
+ )
25
+ end
26
+
27
+ it 'creates context component with preset configuration' do
28
+ expect(helper.ckeditor5_context(preset: :custom)).to have_tag(
29
+ 'ckeditor-context-component',
30
+ with: {
31
+ plugins: '[]',
32
+ config: '{"preset":"custom"}'
33
+ }
34
+ )
35
+ end
36
+
37
+ it 'creates context component with cdn configuration' do
38
+ expect(helper.ckeditor5_context(cdn: :jsdelivr)).to have_tag(
39
+ 'ckeditor-context-component',
40
+ with: {
41
+ plugins: '[]',
42
+ config: '{"cdn":"jsdelivr"}'
43
+ }
44
+ )
45
+ end
46
+
47
+ it 'creates context component with multiple configurations' do
48
+ result = helper.ckeditor5_context(preset: :custom, cdn: :jsdelivr)
49
+
50
+ expect(result).to have_tag(
51
+ 'ckeditor-context-component',
52
+ with: {
53
+ plugins: '[]',
54
+ config: '{"preset":"custom","cdn":"jsdelivr"}'
55
+ }
56
+ )
57
+ end
58
+
59
+ it 'accepts block content' do
60
+ result = helper.ckeditor5_context { 'Content' }
61
+
62
+ expect(result).to have_tag('ckeditor-context-component') do
63
+ with_text 'Content'
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe CKEditor5::Rails::Context::Props do
6
+ let(:config) do
7
+ {
8
+ plugins: [
9
+ CKEditor5::Rails::Editor::PropsPlugin.new('Plugin1', import_name: '@ckeditor/plugin1'),
10
+ CKEditor5::Rails::Editor::PropsInlinePlugin.new('plugin2', 'export default class Plugin2 {}')
11
+ ],
12
+ toolbar: { items: %w[bold italic] },
13
+ language: 'en'
14
+ }
15
+ end
16
+
17
+ subject(:props) { described_class.new(config) }
18
+
19
+ describe '#initialize' do
20
+ it 'accepts a config hash' do
21
+ expect { described_class.new({}) }.not_to raise_error
22
+ end
23
+ end
24
+
25
+ describe '#to_attributes' do
26
+ subject(:attributes) { props.to_attributes }
27
+
28
+ it 'returns a hash with plugins and config keys' do
29
+ expect(attributes).to be_a(Hash)
30
+ expect(attributes.keys).to match_array(%i[plugins config])
31
+ end
32
+
33
+ describe ':plugins key' do
34
+ subject(:plugins_json) { attributes[:plugins] }
35
+
36
+ it 'serializes plugins array to JSON' do
37
+ expect(plugins_json).to be_a(String)
38
+ expect(JSON.parse(plugins_json)).to be_an(Array)
39
+ end
40
+
41
+ it 'normalizes and includes all plugins' do
42
+ plugins = JSON.parse(plugins_json)
43
+ expect(plugins.size).to eq(2)
44
+ expect(plugins.first).to include(
45
+ 'type' => 'external',
46
+ 'import_name' => '@ckeditor/plugin1'
47
+ )
48
+ expect(plugins.last).to include(
49
+ 'type' => 'inline',
50
+ 'name' => 'plugin2',
51
+ 'code' => 'export default class Plugin2 {}'
52
+ )
53
+ end
54
+ end
55
+
56
+ describe ':config key' do
57
+ subject(:config_json) { attributes[:config] }
58
+
59
+ it 'serializes config to JSON excluding plugins' do
60
+ expect(config_json).to be_a(String)
61
+ parsed = JSON.parse(config_json)
62
+ expect(parsed).to include(
63
+ 'toolbar' => { 'items' => %w[bold italic] },
64
+ 'language' => 'en'
65
+ )
66
+ expect(parsed).not_to include('plugins')
67
+ end
68
+ end
69
+ end
70
+ end