ckeditor5 1.15.8 → 1.15.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +6 -5
- data/README.md +3 -0
- data/lib/ckeditor5/rails/cdn/ckbox_bundle.rb +2 -2
- data/lib/ckeditor5/rails/cdn/helpers.rb +6 -0
- data/lib/ckeditor5/rails/context/helpers.rb +1 -1
- data/lib/ckeditor5/rails/editor/editable_height_normalizer.rb +50 -0
- data/lib/ckeditor5/rails/editor/helpers/config_helpers.rb +3 -3
- data/lib/ckeditor5/rails/editor/helpers/editor_helpers.rb +5 -4
- data/lib/ckeditor5/rails/editor/props.rb +3 -20
- data/lib/ckeditor5/rails/plugins/simple_upload_adapter.rb +1 -1
- data/lib/ckeditor5/rails/presets/preset_builder.rb +2 -3
- data/lib/ckeditor5/rails/version.rb +1 -1
- data/lib/ckeditor5/rails/version_detector.rb +6 -0
- data/spec/lib/ckeditor5/rails/assets/asset_bundle_hml_serializer_spec.rb +104 -0
- data/spec/lib/ckeditor5/rails/assets/assets_bundle_spec.rb +191 -0
- data/spec/lib/ckeditor5/rails/cdn/ckbox_bundle_spec.rb +69 -0
- data/spec/lib/ckeditor5/rails/cdn/ckeditor_bundle_spec.rb +72 -0
- data/spec/lib/ckeditor5/rails/cdn/helpers_spec.rb +217 -0
- data/spec/lib/ckeditor5/rails/context/helpers_spec.rb +67 -0
- data/spec/lib/ckeditor5/rails/context/props_spec.rb +70 -0
- data/spec/lib/ckeditor5/rails/editor/editable_height_normalizer_spec.rb +50 -0
- data/spec/lib/ckeditor5/rails/editor/helpers/config_helpers_spec.rb +52 -0
- data/spec/lib/ckeditor5/rails/editor/helpers/editor_helpers_spec.rb +192 -0
- data/spec/lib/ckeditor5/rails/editor/props_inline_plugin_spec.rb +43 -0
- data/spec/lib/ckeditor5/rails/editor/props_plugin_spec.rb +66 -0
- data/spec/lib/ckeditor5/rails/editor/props_spec.rb +104 -0
- data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +47 -0
- data/spec/lib/ckeditor5/rails/presets/manager_spec.rb +100 -0
- data/spec/lib/ckeditor5/rails/presets/plugins_builder_spec.rb +98 -0
- data/spec/lib/ckeditor5/rails/presets/preset_builder_spec.rb +337 -0
- data/spec/lib/ckeditor5/rails/presets/toolbar_builder_spec.rb +70 -0
- data/spec/lib/ckeditor5/rails/semver_spec.rb +58 -0
- data/spec/lib/ckeditor5/rails/version_detector_spec.rb +131 -0
- data/spec/lib/ckeditor5/rails/version_spec.rb +25 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/support/test_models.rb +6 -0
- metadata +44 -1
@@ -0,0 +1,337 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::Presets::PresetBuilder do
|
6
|
+
let(:builder) { described_class.new }
|
7
|
+
|
8
|
+
describe '#initialize' do
|
9
|
+
it 'sets default values' do
|
10
|
+
expect(builder.version).to be_nil
|
11
|
+
expect(builder.premium?).to be false
|
12
|
+
expect(builder.cdn).to eq(:jsdelivr)
|
13
|
+
expect(builder.translations).to eq([:en])
|
14
|
+
expect(builder.license_key).to be_nil
|
15
|
+
expect(builder.type).to eq(:classic)
|
16
|
+
expect(builder.ckbox).to be_nil
|
17
|
+
expect(builder.editable_height).to be_nil
|
18
|
+
expect(builder.config).to eq({ plugins: [], toolbar: [] })
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'accepts a configuration block' do
|
22
|
+
builder = described_class.new do
|
23
|
+
version '35.0.0'
|
24
|
+
premium true
|
25
|
+
translations :en, :pl
|
26
|
+
end
|
27
|
+
|
28
|
+
expect(builder.version).to eq('35.0.0')
|
29
|
+
expect(builder.premium?).to be true
|
30
|
+
expect(builder.translations).to eq(%i[en pl])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#initialize_copy' do
|
35
|
+
let(:original) do
|
36
|
+
described_class.new do
|
37
|
+
version '35.0.0'
|
38
|
+
translations :en, :pl
|
39
|
+
ckbox '1.0.0'
|
40
|
+
toolbar :bold, :italic
|
41
|
+
plugins :Essentials
|
42
|
+
end
|
43
|
+
end
|
44
|
+
let(:copy) { original.dup }
|
45
|
+
|
46
|
+
it 'creates a deep copy' do
|
47
|
+
expect(copy.translations.object_id).not_to eq(original.translations.object_id)
|
48
|
+
expect(copy.ckbox.object_id).not_to eq(original.ckbox.object_id)
|
49
|
+
expect(copy.config[:plugins].object_id).not_to eq(original.config[:plugins].object_id)
|
50
|
+
expect(copy.config[:toolbar].object_id).not_to eq(original.config[:toolbar].object_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'maintains the same values' do
|
54
|
+
expect(copy.version).to eq(original.version)
|
55
|
+
expect(copy.translations).to eq(original.translations)
|
56
|
+
expect(copy.ckbox).to eq(original.ckbox)
|
57
|
+
|
58
|
+
# Compare toolbar separately
|
59
|
+
expect(copy.config[:toolbar][:items]).to eq(original.config[:toolbar][:items])
|
60
|
+
expect(copy.config[:toolbar][:shouldNotGroupWhenFull]).to eq(
|
61
|
+
original.config[:toolbar][:shouldNotGroupWhenFull]
|
62
|
+
)
|
63
|
+
|
64
|
+
# Compare plugin names
|
65
|
+
copy_names = copy.config[:plugins].map { |p| p.name.to_s }
|
66
|
+
original_names = original.config[:plugins].map { |p| p.name.to_s }
|
67
|
+
expect(copy_names).to eq(original_names)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'configuration methods' do
|
72
|
+
describe '#automatic_upgrades' do
|
73
|
+
it 'enables automatic upgrades' do
|
74
|
+
builder.automatic_upgrades
|
75
|
+
expect(builder.automatic_upgrades?).to be true
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'detects latest safe version when enabled' do
|
79
|
+
allow(CKEditor5::Rails::VersionDetector).to receive(:latest_safe_version)
|
80
|
+
.with('35.0.0')
|
81
|
+
.and_return('35.1.0')
|
82
|
+
|
83
|
+
builder.automatic_upgrades
|
84
|
+
builder.version '35.0.0'
|
85
|
+
|
86
|
+
expect(builder.version).to eq('35.1.0')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#license_key' do
|
91
|
+
it 'sets license key and switches to cloud CDN for non-GPL licenses' do
|
92
|
+
builder.license_key('commercial-key')
|
93
|
+
expect(builder.license_key).to eq('commercial-key')
|
94
|
+
expect(builder.cdn).to eq(:cloud)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'keeps current CDN for GPL license' do
|
98
|
+
builder.cdn(:jsdelivr)
|
99
|
+
builder.license_key('GPL')
|
100
|
+
expect(builder.cdn).to eq(:jsdelivr)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#menubar' do
|
105
|
+
it 'configures menubar visibility' do
|
106
|
+
builder.menubar(visible: true)
|
107
|
+
expect(builder.config[:menuBar]).to eq({ isVisible: true })
|
108
|
+
expect(builder.menubar?).to be true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#toolbar' do
|
113
|
+
it 'configures toolbar items' do
|
114
|
+
builder.toolbar(:bold, :italic, should_group_when_full: false)
|
115
|
+
expect(builder.config[:toolbar]).to eq({
|
116
|
+
items: %i[bold italic],
|
117
|
+
shouldNotGroupWhenFull: true
|
118
|
+
})
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'doesn\'t override existing toolbar if no items provided' do
|
122
|
+
original_config = { items: [:bold], shouldNotGroupWhenFull: true }
|
123
|
+
builder.config[:toolbar] = original_config
|
124
|
+
builder.toolbar
|
125
|
+
expect(builder.config[:toolbar]).to eq(original_config)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'overrides existing toolbar if items provided' do
|
129
|
+
builder.config[:toolbar] = { items: [:bold], shouldNotGroupWhenFull: true }
|
130
|
+
builder.toolbar(:italic)
|
131
|
+
expect(builder.config[:toolbar][:items]).to eq([:italic])
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'accepts a configuration block' do
|
135
|
+
builder.toolbar do
|
136
|
+
append :bold, :italic
|
137
|
+
prepend :undo
|
138
|
+
remove :|
|
139
|
+
end
|
140
|
+
expect(builder.config[:toolbar][:items]).to include(:bold, :italic, :undo)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#plugins' do
|
145
|
+
it 'adds plugins' do
|
146
|
+
builder.plugins(:Essentials, :Paragraph)
|
147
|
+
plugin_names = builder.config[:plugins].map { |p| p.name.to_s }
|
148
|
+
expect(plugin_names).to eq(%w[Essentials Paragraph])
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'accepts a configuration block' do
|
152
|
+
builder.plugins do
|
153
|
+
append :Essentials
|
154
|
+
prepend :Paragraph
|
155
|
+
remove :Base64UploadAdapter
|
156
|
+
end
|
157
|
+
plugin_names = builder.config[:plugins].map { |p| p.name.to_s }
|
158
|
+
expect(plugin_names).to eq(%w[Paragraph Essentials])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe '#simple_upload_adapter' do
|
163
|
+
it 'configures simple upload adapter' do
|
164
|
+
builder.simple_upload_adapter('/custom/upload')
|
165
|
+
expect(builder.config[:simpleUpload]).to eq({ uploadUrl: '/custom/upload' })
|
166
|
+
|
167
|
+
plugin_names = builder.config[:plugins].map(&:name)
|
168
|
+
expect(plugin_names).to include(:SimpleUploadAdapter)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '#inline_plugin' do
|
174
|
+
let(:plugin_code) do
|
175
|
+
<<~JAVASCRIPT
|
176
|
+
import Plugin from 'ckeditor5/src/plugin';
|
177
|
+
export default class CustomPlugin extends Plugin {
|
178
|
+
init() {
|
179
|
+
// plugin initialization
|
180
|
+
}
|
181
|
+
}
|
182
|
+
JAVASCRIPT
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'adds inline plugin to configuration' do
|
186
|
+
builder.inline_plugin(:CustomPlugin, plugin_code)
|
187
|
+
|
188
|
+
plugin = builder.config[:plugins].first
|
189
|
+
expect(plugin).to be_a(CKEditor5::Rails::Editor::PropsInlinePlugin)
|
190
|
+
expect(plugin.name).to eq(:CustomPlugin)
|
191
|
+
expect(plugin.code).to eq(plugin_code)
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'allows multiple inline plugins' do
|
195
|
+
builder.inline_plugin(:Plugin1, plugin_code)
|
196
|
+
builder.inline_plugin(:Plugin2, plugin_code)
|
197
|
+
|
198
|
+
plugin_names = builder.config[:plugins].map(&:name)
|
199
|
+
expect(plugin_names).to eq(%i[Plugin1 Plugin2])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe '#cdn' do
|
204
|
+
it 'returns current cdn when called without arguments' do
|
205
|
+
expect(builder.cdn).to eq(:jsdelivr)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'sets cdn when string/symbol provided' do
|
209
|
+
builder.cdn(:cloud)
|
210
|
+
expect(builder.cdn).to eq(:cloud)
|
211
|
+
end
|
212
|
+
|
213
|
+
context 'with block' do
|
214
|
+
it 'accepts block with correct arity' do
|
215
|
+
cdn_block = ->(bundle, version, path) { "#{bundle}/#{version}/#{path}" }
|
216
|
+
builder.cdn(&cdn_block)
|
217
|
+
expect(builder.cdn).to eq(cdn_block)
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'raises error when block has wrong arity' do
|
221
|
+
expect do
|
222
|
+
builder.cdn { |bundle| bundle }
|
223
|
+
end.to raise_error(ArgumentError, 'Block must accept exactly 3 arguments: bundle, version, path')
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'raises error when block has wrong arity (too many args)' do
|
227
|
+
expect do
|
228
|
+
builder.cdn { |bundle, version, path, extra| bundle } # rubocop:disable Lint/UnusedBlockArgument
|
229
|
+
end.to raise_error(ArgumentError, 'Block must accept exactly 3 arguments: bundle, version, path')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe '#type' do
|
235
|
+
it 'returns current type when called without arguments' do
|
236
|
+
expect(builder.type).to eq(:classic)
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'sets type when valid type provided' do
|
240
|
+
allow(CKEditor5::Rails::Editor::Props).to receive(:valid_editor_type?)
|
241
|
+
.with(:inline)
|
242
|
+
.and_return(true)
|
243
|
+
|
244
|
+
builder.type(:inline)
|
245
|
+
expect(builder.type).to eq(:inline)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'raises error when invalid type provided' do
|
249
|
+
allow(CKEditor5::Rails::Editor::Props).to receive(:valid_editor_type?)
|
250
|
+
.with(:invalid)
|
251
|
+
.and_return(false)
|
252
|
+
|
253
|
+
expect do
|
254
|
+
builder.type(:invalid)
|
255
|
+
end.to raise_error(ArgumentError, 'Invalid editor type: invalid')
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#to_h_with_overrides' do
|
260
|
+
let(:builder) do
|
261
|
+
described_class.new do
|
262
|
+
version '35.0.0'
|
263
|
+
premium true
|
264
|
+
translations :en, :pl
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'returns hash with default values' do
|
269
|
+
result = builder.to_h_with_overrides
|
270
|
+
expect(result).to include(
|
271
|
+
version: '35.0.0',
|
272
|
+
premium: true,
|
273
|
+
translations: %i[en pl]
|
274
|
+
)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'applies overrides' do
|
278
|
+
result = builder.to_h_with_overrides(
|
279
|
+
version: '36.0.0',
|
280
|
+
premium: false,
|
281
|
+
translations: [:en]
|
282
|
+
)
|
283
|
+
expect(result).to include(
|
284
|
+
version: '36.0.0',
|
285
|
+
premium: false,
|
286
|
+
translations: [:en]
|
287
|
+
)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
describe '#language' do
|
292
|
+
it 'returns language config when called without arguments' do
|
293
|
+
builder.language(:pl, content: :en)
|
294
|
+
expect(builder.language).to eq({ ui: :pl, content: :en })
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'sets both UI and content language to same value by default' do
|
298
|
+
builder.language(:pl)
|
299
|
+
expect(builder.config[:language]).to eq({ ui: :pl, content: :pl })
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'allows different UI and content languages' do
|
303
|
+
builder.language(:pl, content: :en)
|
304
|
+
expect(builder.config[:language]).to eq({ ui: :pl, content: :en })
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe '#deep_copy_toolbar' do
|
309
|
+
context 'with array toolbar' do
|
310
|
+
it 'returns duplicated array' do
|
311
|
+
original = %i[bold italic]
|
312
|
+
copy = builder.send(:deep_copy_toolbar, original)
|
313
|
+
expect(copy.object_id).not_to eq(original.object_id)
|
314
|
+
expect(copy).to eq(original)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
context 'with hash toolbar' do
|
319
|
+
it 'returns deep copy of toolbar config' do
|
320
|
+
original = {
|
321
|
+
items: %i[bold italic],
|
322
|
+
shouldNotGroupWhenFull: true
|
323
|
+
}
|
324
|
+
copy = builder.send(:deep_copy_toolbar, original)
|
325
|
+
|
326
|
+
expect(copy[:items].object_id).not_to eq(original[:items].object_id)
|
327
|
+
expect(copy).to eq(original)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
context 'with nil toolbar' do
|
332
|
+
it 'returns empty hash' do
|
333
|
+
expect(builder.send(:deep_copy_toolbar, nil)).to eq({})
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::Presets::ToolbarBuilder do
|
6
|
+
let(:items) { %i[bold italic | link] }
|
7
|
+
let(:builder) { described_class.new(items) }
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
it 'creates a builder with given items' do
|
11
|
+
expect(builder.items).to eq(%i[bold italic | link])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#remove' do
|
16
|
+
it 'removes specified items' do
|
17
|
+
builder.remove(:italic, :|)
|
18
|
+
expect(builder.items).to eq(%i[bold link])
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'ignores non-existent items' do
|
22
|
+
builder.remove(:nonexistent)
|
23
|
+
expect(builder.items).to eq(%i[bold italic | link])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#prepend' do
|
28
|
+
context 'without before option' do
|
29
|
+
it 'adds items at the beginning' do
|
30
|
+
builder.prepend(:underline, :strike)
|
31
|
+
expect(builder.items).to eq(%i[underline strike bold italic | link])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with before option' do
|
36
|
+
it 'adds items before specified item' do
|
37
|
+
builder.prepend(:underline, before: :italic)
|
38
|
+
expect(builder.items).to eq(%i[bold underline italic | link])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raises error when target item not found' do
|
42
|
+
expect do
|
43
|
+
builder.prepend(:underline, before: :nonexistent)
|
44
|
+
end.to raise_error(ArgumentError, "Item 'nonexistent' not found in array")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#append' do
|
50
|
+
context 'without after option' do
|
51
|
+
it 'adds items at the end' do
|
52
|
+
builder.append(:underline, :strike)
|
53
|
+
expect(builder.items).to eq(%i[bold italic | link underline strike])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with after option' do
|
58
|
+
it 'adds items after specified item' do
|
59
|
+
builder.append(:underline, after: :italic)
|
60
|
+
expect(builder.items).to eq(%i[bold italic underline | link])
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'raises error when target item not found' do
|
64
|
+
expect do
|
65
|
+
builder.append(:underline, after: :nonexistent)
|
66
|
+
end.to raise_error(ArgumentError, "Item 'nonexistent' not found in array")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -42,4 +42,62 @@ RSpec.describe CKEditor5::Rails::Semver do
|
|
42
42
|
expect(semver.version).to eq(version)
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
describe '#<=>' do
|
47
|
+
let(:version1) { described_class.new('1.2.3') }
|
48
|
+
|
49
|
+
it 'compares versions correctly' do
|
50
|
+
expect(version1).to be < described_class.new('1.2.4')
|
51
|
+
expect(version1).to be < described_class.new('1.3.0')
|
52
|
+
expect(version1).to be < described_class.new('2.0.0')
|
53
|
+
expect(version1).to be > described_class.new('1.2.2')
|
54
|
+
expect(version1).to be > described_class.new('1.1.9')
|
55
|
+
expect(version1).to be > described_class.new('0.9.9')
|
56
|
+
expect(version1).to eq described_class.new('1.2.3')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'returns nil when comparing with non-Semver object' do
|
60
|
+
expect(version1 <=> 'not a version').to be_nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#safe_update?' do
|
65
|
+
let(:base_version) { described_class.new('1.2.3') }
|
66
|
+
|
67
|
+
context 'when major version changes' do
|
68
|
+
it 'returns false for major version increase' do
|
69
|
+
expect(base_version.safe_update?('2.0.0')).to be false
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns false for major version decrease' do
|
73
|
+
expect(base_version.safe_update?('0.2.3')).to be false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when minor version changes' do
|
78
|
+
it 'returns true for minor version increase' do
|
79
|
+
expect(base_version.safe_update?('1.3.0')).to be true
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns false for minor version decrease' do
|
83
|
+
expect(base_version.safe_update?('1.1.9')).to be false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when patch version changes' do
|
88
|
+
it 'returns true for patch version increase' do
|
89
|
+
expect(base_version.safe_update?('1.2.4')).to be true
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns false for patch version decrease' do
|
93
|
+
expect(base_version.safe_update?('1.2.2')).to be false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'when version is the same' do
|
98
|
+
it 'returns false for identical version' do
|
99
|
+
expect(base_version.safe_update?('1.2.3')).to be false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
45
103
|
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::VersionDetector do
|
6
|
+
let(:current_version) { '35.1.0' }
|
7
|
+
|
8
|
+
let(:npm_response) do
|
9
|
+
{
|
10
|
+
'versions' => {
|
11
|
+
'34.0.0' => {},
|
12
|
+
'34.1.0' => {},
|
13
|
+
'35.0.0' => {},
|
14
|
+
'35.1.0' => {},
|
15
|
+
'35.2.0' => {},
|
16
|
+
'36.0.0' => {},
|
17
|
+
'35.2.1-dev' => {},
|
18
|
+
'36.0.0-nightly' => {}
|
19
|
+
}
|
20
|
+
}.to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:success_response) do
|
24
|
+
double('Net::HTTPSuccess',
|
25
|
+
body: npm_response,
|
26
|
+
is_a?: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
described_class.instance.clear_cache!
|
31
|
+
|
32
|
+
allow(Net::HTTP).to receive(:new).and_return(
|
33
|
+
double('Net::HTTP').tap do |http|
|
34
|
+
allow(http).to receive(:use_ssl=)
|
35
|
+
allow(http).to receive(:open_timeout=)
|
36
|
+
allow(http).to receive(:read_timeout=)
|
37
|
+
allow(http).to receive(:get).and_return(success_response)
|
38
|
+
end
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '.latest_safe_version' do
|
43
|
+
before do
|
44
|
+
described_class.instance.clear_cache!
|
45
|
+
setup_http_mock(success_response)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns the latest safe version' do
|
49
|
+
expect(described_class.latest_safe_version(current_version)).to eq('35.2.0')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'caches the result' do
|
53
|
+
first_call = described_class.latest_safe_version(current_version)
|
54
|
+
|
55
|
+
# Simulate different NPM response
|
56
|
+
allow(success_response).to receive(:body).and_return(
|
57
|
+
{ 'versions' => { '35.3.0' => {} } }.to_json
|
58
|
+
)
|
59
|
+
|
60
|
+
second_call = described_class.latest_safe_version(current_version)
|
61
|
+
|
62
|
+
expect(first_call).to eq(second_call)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'error handling' do
|
67
|
+
before do
|
68
|
+
described_class.instance.clear_cache!
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when HTTP request fails' do
|
72
|
+
before do
|
73
|
+
allow(Net::HTTP).to receive(:new).and_raise(StandardError.new('Connection failed'))
|
74
|
+
allow_any_instance_of(Kernel).to receive(:warn)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns nil and logs warning' do
|
78
|
+
expect_any_instance_of(Kernel).to receive(:warn).with('Error fetching versions: Connection failed')
|
79
|
+
expect(described_class.latest_safe_version(current_version)).to be_nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when response is not successful' do
|
84
|
+
let(:error_response) do
|
85
|
+
double('Net::HTTPNotFound',
|
86
|
+
is_a?: false)
|
87
|
+
end
|
88
|
+
|
89
|
+
before do
|
90
|
+
setup_http_mock(error_response)
|
91
|
+
allow_any_instance_of(Kernel).to receive(:warn)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'returns nil and logs warning' do
|
95
|
+
expect_any_instance_of(Kernel).to receive(:warn).with('Failed to fetch CKEditor versions')
|
96
|
+
expect(described_class.latest_safe_version(current_version)).to be_nil
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'version filtering' do
|
102
|
+
before do
|
103
|
+
described_class.instance.clear_cache!
|
104
|
+
setup_http_mock(success_response)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'ignores nightly and dev versions' do
|
108
|
+
latest = described_class.latest_safe_version(current_version)
|
109
|
+
expect(latest).not_to include('nightly')
|
110
|
+
expect(latest).not_to include('dev')
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'only returns versions safe to update to' do
|
114
|
+
expect(described_class.latest_safe_version('35.0.0')).to eq('35.2.0')
|
115
|
+
expect(described_class.latest_safe_version('34.0.0')).to eq('34.1.0')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def setup_http_mock(response) # rubocop:disable Metrics/AbcSize
|
122
|
+
allow(Net::HTTP).to receive(:new).and_return(
|
123
|
+
double('Net::HTTP').tap do |http|
|
124
|
+
allow(http).to receive(:use_ssl=)
|
125
|
+
allow(http).to receive(:open_timeout=)
|
126
|
+
allow(http).to receive(:read_timeout=)
|
127
|
+
allow(http).to receive(:get).and_return(response)
|
128
|
+
end
|
129
|
+
)
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails do
|
6
|
+
describe 'VERSION' do
|
7
|
+
it 'is defined as a string' do
|
8
|
+
expect(described_class::VERSION).to be_a(String)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'follows semantic versioning format' do
|
12
|
+
expect(described_class::VERSION).to match(/^\d+\.\d+\.\d+$/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'DEFAULT_CKEDITOR_VERSION' do
|
17
|
+
it 'is defined as a string' do
|
18
|
+
expect(described_class::DEFAULT_CKEDITOR_VERSION).to be_a(String)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'follows semantic versioning format' do
|
22
|
+
expect(described_class::DEFAULT_CKEDITOR_VERSION).to match(/^\d+\.\d+\.\d+$/)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,12 +8,13 @@ SimpleCov.start do
|
|
8
8
|
enable_coverage :line
|
9
9
|
|
10
10
|
add_filter 'sandbox/'
|
11
|
+
add_filter 'spec/'
|
12
|
+
add_filter 'lib/ckeditor5/rails/version.rb' # Fix bug in coverage calculation
|
11
13
|
add_group 'Library', 'lib/'
|
12
14
|
|
13
15
|
track_files 'lib/**/*.rb'
|
14
16
|
|
15
|
-
|
16
|
-
# minimum_coverage_by_file 80
|
17
|
+
minimum_coverage 98
|
17
18
|
|
18
19
|
formatters = [
|
19
20
|
SimpleCov::Formatter::HTMLFormatter,
|
@@ -31,6 +32,9 @@ require 'pry'
|
|
31
32
|
require 'spec_helper'
|
32
33
|
require 'rspec/rails'
|
33
34
|
require 'rspec/expectations'
|
35
|
+
require 'rspec-html-matchers'
|
36
|
+
|
37
|
+
Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require f }
|
34
38
|
|
35
39
|
Rails.application.initialize!
|
36
40
|
|
@@ -57,4 +61,6 @@ RSpec.configure do |config|
|
|
57
61
|
config.before(:each) do
|
58
62
|
Rails.application.load_seed
|
59
63
|
end
|
64
|
+
|
65
|
+
config.include RSpecHtmlMatchers
|
60
66
|
end
|