ckeditor5 1.15.9 → 1.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/lib/ckeditor5/rails/assets/webcomponents/components/context.mjs +23 -0
- data/lib/ckeditor5/rails/assets/webcomponents/components/editor.mjs +24 -0
- data/lib/ckeditor5/rails/assets/webcomponents/utils.mjs +33 -10
- data/lib/ckeditor5/rails/concerns/checksum.rb +15 -0
- data/lib/ckeditor5/rails/context/props.rb +17 -2
- data/lib/ckeditor5/rails/editor/props.rb +16 -3
- data/lib/ckeditor5/rails/editor/props_base_plugin.rb +19 -0
- data/lib/ckeditor5/rails/editor/props_inline_plugin.rb +6 -3
- data/lib/ckeditor5/rails/editor/props_plugin.rb +6 -4
- data/lib/ckeditor5/rails/engine.rb +3 -2
- data/lib/ckeditor5/rails/presets/plugins_builder.rb +9 -9
- data/lib/ckeditor5/rails/presets/preset_builder.rb +4 -6
- data/lib/ckeditor5/rails/version.rb +1 -1
- data/lib/ckeditor5/rails.rb +1 -0
- data/spec/e2e/features/editor_types_spec.rb +178 -0
- data/spec/e2e/features/form_integration_spec.rb +60 -0
- data/spec/e2e/spec_helper.rb +43 -0
- data/spec/e2e/support/eventually.rb +14 -0
- data/spec/e2e/support/form_helpers.rb +37 -0
- data/spec/lib/ckeditor5/rails/assets/asset_bundle_hml_serializer_spec.rb +7 -0
- data/spec/lib/ckeditor5/rails/cdn/helpers_spec.rb +19 -1
- data/spec/lib/ckeditor5/rails/concerns/checksum_spec.rb +50 -0
- data/spec/lib/ckeditor5/rails/context/props_spec.rb +7 -1
- data/spec/lib/ckeditor5/rails/editor/props_base_plugin_spec.rb +27 -0
- data/spec/lib/ckeditor5/rails/editor/props_spec.rb +2 -0
- data/spec/lib/ckeditor5/rails/engine_spec.rb +88 -0
- data/spec/lib/ckeditor5/rails/hooks/form_spec.rb +156 -0
- data/spec/lib/ckeditor5/rails/hooks/simple_form_spec.rb +100 -0
- data/spec/lib/ckeditor5/rails/presets/plugins_builder_spec.rb +10 -10
- data/spec/lib/ckeditor5/rails/presets/preset_builder_spec.rb +10 -0
- data/spec/spec_helper.rb +2 -2
- metadata +22 -2
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'e2e/spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Form Integration', type: :feature, js: true do
|
6
|
+
before do
|
7
|
+
visit('form')
|
8
|
+
setup_form_tracking(page)
|
9
|
+
end
|
10
|
+
|
11
|
+
shared_examples 'a form with CKEditor' do |form_testid, editor_testid, submit_testid| # 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
|
+
|
18
|
+
it 'loads editor properly' do
|
19
|
+
expect(page).to have_css("[data-testid='#{editor_testid}'] .ck-editor__editable")
|
20
|
+
expect(editor).to have_invisible_textarea
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'validates required fields' do
|
24
|
+
editable.click
|
25
|
+
editable.send_keys([[:control, 'a'], :backspace])
|
26
|
+
|
27
|
+
text_field.set('')
|
28
|
+
submit_button.click
|
29
|
+
|
30
|
+
expect(form).not_to have_been_submitted
|
31
|
+
expect(text_field).to be_invalid
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'submits with valid data' do
|
35
|
+
editable.click
|
36
|
+
editable.send_keys('New content')
|
37
|
+
text_field.set('Second field value')
|
38
|
+
|
39
|
+
submit_button.click
|
40
|
+
|
41
|
+
eventually do
|
42
|
+
expect(form).to have_been_submitted
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'Rails form' do
|
48
|
+
it_behaves_like 'a form with CKEditor',
|
49
|
+
'rails-form',
|
50
|
+
'rails-form-editor',
|
51
|
+
'rails-form-submit'
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'Simple form' do
|
55
|
+
it_behaves_like 'a form with CKEditor',
|
56
|
+
'simple-form',
|
57
|
+
'simple-form-editor',
|
58
|
+
'simple-form-submit'
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'capybara'
|
4
|
+
require 'capybara/rspec'
|
5
|
+
require 'capybara/cuprite'
|
6
|
+
|
7
|
+
ENV['RAILS_ENV'] ||= 'test'
|
8
|
+
|
9
|
+
require File.expand_path('../../sandbox/config/environment', __dir__)
|
10
|
+
|
11
|
+
require 'capybara/rails'
|
12
|
+
|
13
|
+
Capybara.app = Rails.application
|
14
|
+
|
15
|
+
Capybara.register_driver(:cuprite) do |app|
|
16
|
+
driver = Capybara::Cuprite::Driver.new(
|
17
|
+
app,
|
18
|
+
window_size: [1200, 800],
|
19
|
+
headless: ENV['HEADLESS'] == 'true',
|
20
|
+
browser_options: {
|
21
|
+
'no-sandbox': nil,
|
22
|
+
'disable-gpu': nil,
|
23
|
+
'enable-logging': nil
|
24
|
+
},
|
25
|
+
process_timeout: 20,
|
26
|
+
timeout: 20,
|
27
|
+
inspector: true
|
28
|
+
)
|
29
|
+
|
30
|
+
process = driver.browser.process
|
31
|
+
puts ''
|
32
|
+
puts "Browser: #{process.browser_version}"
|
33
|
+
puts "Protocol: #{process.protocol_version}"
|
34
|
+
puts "V8: #{process.v8_version}"
|
35
|
+
puts "Webkit: #{process.webkit_version}"
|
36
|
+
driver
|
37
|
+
end
|
38
|
+
|
39
|
+
Capybara.server = :webrick
|
40
|
+
Capybara.default_driver = :cuprite
|
41
|
+
Capybara.javascript_driver = :cuprite
|
42
|
+
|
43
|
+
Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require f }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Add eventually helper for async operations
|
4
|
+
def eventually(timeout: 5, delay: 0.1)
|
5
|
+
deadline = Time.zone.now + timeout
|
6
|
+
loop do
|
7
|
+
yield
|
8
|
+
break
|
9
|
+
rescue RSpec::Expectations::ExpectationNotMetError, StandardError => e
|
10
|
+
raise e if Time.zone.now >= deadline
|
11
|
+
|
12
|
+
sleep delay
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FormHelpers
|
4
|
+
def setup_form_tracking(driver)
|
5
|
+
driver.execute_script <<~JS
|
6
|
+
window.lastSubmittedForm = null;
|
7
|
+
|
8
|
+
document.addEventListener('submit', (e) => {
|
9
|
+
e.preventDefault();
|
10
|
+
window.lastSubmittedForm = e.target.id;
|
11
|
+
});
|
12
|
+
JS
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.include FormHelpers, type: :feature
|
18
|
+
end
|
19
|
+
|
20
|
+
RSpec::Matchers.define :be_invalid do
|
21
|
+
match do |element|
|
22
|
+
element[:validity] == 'false' ||
|
23
|
+
element.evaluate_script('!this.validity.valid')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec::Matchers.define :have_been_submitted do
|
28
|
+
match do |form|
|
29
|
+
page.evaluate_script('window.lastSubmittedForm') == form['id']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec::Matchers.define :have_invisible_textarea do
|
34
|
+
match do |element|
|
35
|
+
element.has_css?('textarea', visible: :hidden)
|
36
|
+
end
|
37
|
+
end
|
@@ -86,6 +86,13 @@ RSpec.describe CKEditor5::Rails::Assets::AssetsBundleHtmlSerializer do
|
|
86
86
|
it 'includes web component script' do
|
87
87
|
expect(html).to include('<script type="module" nonce="true">')
|
88
88
|
end
|
89
|
+
|
90
|
+
it 'memoizes scripts import map' do
|
91
|
+
first_call = serializer.send(:scripts_import_map_tag)
|
92
|
+
second_call = serializer.send(:scripts_import_map_tag)
|
93
|
+
|
94
|
+
expect(first_call.object_id).to eq(second_call.object_id)
|
95
|
+
end
|
89
96
|
end
|
90
97
|
|
91
98
|
describe '.url_resource_preload_type' do
|
@@ -160,7 +160,25 @@ RSpec.describe CKEditor5::Rails::Cdn::Helpers do
|
|
160
160
|
)
|
161
161
|
end
|
162
162
|
|
163
|
-
|
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
|
164
182
|
expect { helper.ckeditor5_assets(preset: :default) }
|
165
183
|
.to raise_error(ArgumentError, /forgot to define version/)
|
166
184
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::Concerns::Checksum do
|
6
|
+
let(:dummy_class) do
|
7
|
+
Class.new do
|
8
|
+
include CKEditor5::Rails::Concerns::Checksum
|
9
|
+
|
10
|
+
public :calculate_object_checksum
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
subject(:instance) { dummy_class.new }
|
15
|
+
|
16
|
+
describe '#calculate_object_checksum' do
|
17
|
+
it 'returns a 16-character string' do
|
18
|
+
result = instance.calculate_object_checksum({ test: 'value' })
|
19
|
+
expect(result).to eq(
|
20
|
+
'f98be16ebfa861cb39a61faff9e52b33f5bcc16bb6ae72e728d226dc07093932'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns consistent checksums for the same input' do
|
25
|
+
input = { name: 'test', value: 123 }
|
26
|
+
first_result = instance.calculate_object_checksum(input)
|
27
|
+
second_result = instance.calculate_object_checksum(input)
|
28
|
+
expect(first_result).to eq(second_result)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns different checksums for different inputs' do
|
32
|
+
result1 = instance.calculate_object_checksum({ a: 1 })
|
33
|
+
result2 = instance.calculate_object_checksum({ a: 2 })
|
34
|
+
expect(result1).not_to eq(result2)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'handles arrays' do
|
38
|
+
result = instance.calculate_object_checksum([1, 2, 3])
|
39
|
+
expect(result).to eq(
|
40
|
+
'a615eeaee21de5179de080de8c3052c8da901138406ba71c38c032845f7d54f4'
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'is order dependent for hashes' do
|
45
|
+
result1 = instance.calculate_object_checksum({ a: 1, b: 2 })
|
46
|
+
result2 = instance.calculate_object_checksum({ b: 2, a: 1 })
|
47
|
+
expect(result1).not_to eq(result2)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -25,9 +25,15 @@ RSpec.describe CKEditor5::Rails::Context::Props do
|
|
25
25
|
describe '#to_attributes' do
|
26
26
|
subject(:attributes) { props.to_attributes }
|
27
27
|
|
28
|
+
it 'returns integrity property' do
|
29
|
+
expect(attributes[:integrity]).to eq(
|
30
|
+
'24e46c3ee19f6764930b38ecdf62c0ac824a0acbe6616b46199d892afb211acb'
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
28
34
|
it 'returns a hash with plugins and config keys' do
|
29
35
|
expect(attributes).to be_a(Hash)
|
30
|
-
expect(attributes.keys).to match_array(%i[plugins config])
|
36
|
+
expect(attributes.keys).to match_array(%i[plugins integrity config])
|
31
37
|
end
|
32
38
|
|
33
39
|
describe ':plugins key' do
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::Editor::PropsBasePlugin do
|
6
|
+
let(:concrete_class) do
|
7
|
+
Class.new(described_class) do
|
8
|
+
def to_unsafe_h
|
9
|
+
{ type: :test, name: name }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:instance) { concrete_class.new(:TestPlugin) }
|
15
|
+
|
16
|
+
describe '#initialize' do
|
17
|
+
it 'sets the name attribute' do
|
18
|
+
expect(instance.name).to eq(:TestPlugin)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#to_h' do
|
23
|
+
it 'raises NotImplementedError' do
|
24
|
+
expect { instance.to_h }.to raise_error(NotImplementedError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -28,11 +28,13 @@ RSpec.describe CKEditor5::Rails::Editor::Props do
|
|
28
28
|
|
29
29
|
it 'includes required attributes' do
|
30
30
|
attributes = props.to_attributes
|
31
|
+
|
31
32
|
expect(attributes).to include(
|
32
33
|
type: 'ClassicEditor',
|
33
34
|
translations: String,
|
34
35
|
plugins: String,
|
35
36
|
config: String,
|
37
|
+
integrity: '358d88b83d041f208d94ac957b2fd68135f1caab5c0d101d33cf04d5d39d81ef',
|
36
38
|
watchdog: true
|
37
39
|
)
|
38
40
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::Engine do
|
6
|
+
describe 'configuration' do
|
7
|
+
let(:preset) { instance_double(CKEditor5::Rails::Presets::PresetBuilder) }
|
8
|
+
let(:preset_manager) { instance_double(CKEditor5::Rails::Presets::Manager) }
|
9
|
+
|
10
|
+
before do
|
11
|
+
allow(described_class).to receive(:base).and_return(ActiveSupport::OrderedOptions.new)
|
12
|
+
allow(described_class.base).to receive(:presets).and_return(preset_manager)
|
13
|
+
allow(preset_manager).to receive(:default).and_return(preset)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has default configuration' do
|
17
|
+
expect(described_class.base).to be_a(ActiveSupport::OrderedOptions)
|
18
|
+
|
19
|
+
default_preset = described_class.default_preset
|
20
|
+
|
21
|
+
expect(default_preset.type).to eq(:classic)
|
22
|
+
expect(default_preset.toolbar.items).to include(:undo, :redo, :'|', :heading)
|
23
|
+
expect(default_preset.plugins.items.map(&:name)).to include(:Essentials, :Paragraph, :Heading)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '.configure' do
|
27
|
+
it 'yields configuration proxy' do
|
28
|
+
yielded_config = nil
|
29
|
+
described_class.configure do |config|
|
30
|
+
yielded_config = config
|
31
|
+
end
|
32
|
+
expect(yielded_config).to be_a(described_class::ConfigurationProxy)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'allows configuring default preset' do
|
36
|
+
described_class.configure do
|
37
|
+
automatic_upgrades enabled: false
|
38
|
+
version '35.0.0'
|
39
|
+
license_key '1234567'
|
40
|
+
end
|
41
|
+
|
42
|
+
expect(described_class.default_preset.version).to eq('35.0.0')
|
43
|
+
expect(described_class.default_preset.license_key).to eq('1234567')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '.find_preset' do
|
48
|
+
before do
|
49
|
+
allow(preset_manager).to receive(:[]).with(:custom).and_return(preset)
|
50
|
+
allow(preset_manager).to receive(:[]).with(
|
51
|
+
kind_of(CKEditor5::Rails::Presets::PresetBuilder)
|
52
|
+
).and_return(preset)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns preset instance if provided' do
|
56
|
+
test_preset = CKEditor5::Rails::Presets::PresetBuilder.new
|
57
|
+
expect(described_class.find_preset(test_preset)).to eq(test_preset)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'looks up preset by name' do
|
61
|
+
expect(described_class.find_preset(:custom)).to eq(preset)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'initializers' do
|
67
|
+
describe 'helper initializer' do
|
68
|
+
it 'includes helpers in ActionView and ActionController' do
|
69
|
+
expect(ActionView::Base.included_modules).to include(CKEditor5::Rails::Helpers)
|
70
|
+
expect(ActionController::Base.included_modules).to include(CKEditor5::Rails::Helpers)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'form_builder initializer' do
|
75
|
+
it 'includes FormBuilderExtension in ActionView::Helpers::FormBuilder' do
|
76
|
+
expect(ActionView::Helpers::FormBuilder.included_modules)
|
77
|
+
.to include(CKEditor5::Rails::Hooks::Form::FormBuilderExtension)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'simple_form initializer', if: defined?(SimpleForm) do
|
82
|
+
it 'registers ckeditor5 input type' do
|
83
|
+
expect(SimpleForm::FormBuilder.mappings[:ckeditor5])
|
84
|
+
.to eq(CKEditor5::Rails::Hooks::SimpleForm::CKEditor5Input)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -42,6 +42,162 @@ RSpec.describe CKEditor5::Rails::Hooks::Form do
|
|
42
42
|
})
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
context 'with as parameter' do
|
47
|
+
subject(:rendered_editor) do
|
48
|
+
builder.build_editor(:content, as: 'custom_field')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'uses custom field name' do
|
52
|
+
expect(rendered_editor).to have_tag('ckeditor-component', with: {
|
53
|
+
name: 'post[custom_field]'
|
54
|
+
})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'without object_name' do
|
59
|
+
let(:builder) { described_class.new('', post, template) }
|
60
|
+
|
61
|
+
it 'uses method name as field name' do
|
62
|
+
expect(rendered_editor).to have_tag('ckeditor-component', with: {
|
63
|
+
name: 'content'
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with as parameter' do
|
68
|
+
subject(:rendered_editor) do
|
69
|
+
builder.build_editor(:content, as: 'custom_field')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'uses as parameter as field name' do
|
73
|
+
expect(rendered_editor).to have_tag('ckeditor-component', with: {
|
74
|
+
name: 'custom_field'
|
75
|
+
})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'with initial data handling' do
|
81
|
+
before do
|
82
|
+
allow(template).to receive(:ckeditor5_editor)
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when object responds to the method' do
|
86
|
+
it 'passes object method value as initial_data' do
|
87
|
+
builder.build_editor(:content)
|
88
|
+
|
89
|
+
expect(template).to have_received(:ckeditor5_editor)
|
90
|
+
.with(hash_including(initial_data: 'Initial content'))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when object does not respond to the method' do
|
95
|
+
let(:post) { double('Post') }
|
96
|
+
|
97
|
+
it 'passes options initial_data value' do
|
98
|
+
builder.build_editor(:content, initial_data: 'Provided content')
|
99
|
+
|
100
|
+
expect(template).to have_received(:ckeditor5_editor)
|
101
|
+
.with(hash_including(initial_data: 'Provided content'))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'with validation classes handling' do
|
107
|
+
before do
|
108
|
+
allow(template).to receive(:ckeditor5_editor)
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'when object has errors on the field' do
|
112
|
+
let(:post) do
|
113
|
+
instance_double('Post', errors: { content: ['is invalid'] })
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'adds is-invalid class' do
|
117
|
+
builder.build_editor(:content, class: 'custom-class')
|
118
|
+
|
119
|
+
expect(template).to have_received(:ckeditor5_editor)
|
120
|
+
.with(hash_including(class: 'custom-class is-invalid'))
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'adds is-invalid class when no initial class exists' do
|
124
|
+
builder.build_editor(:content)
|
125
|
+
|
126
|
+
expect(template).to have_received(:ckeditor5_editor)
|
127
|
+
.with(hash_including(class: 'is-invalid'))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when object has no errors' do
|
132
|
+
let(:post) do
|
133
|
+
instance_double('Post', errors: {})
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'keeps original class unchanged' do
|
137
|
+
builder.build_editor(:content, class: 'custom-class')
|
138
|
+
|
139
|
+
expect(template).to have_received(:ckeditor5_editor)
|
140
|
+
.with(hash_including(class: 'custom-class'))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'when object does not respond to errors' do
|
145
|
+
let(:post) { double('Post') }
|
146
|
+
|
147
|
+
it 'keeps original class unchanged' do
|
148
|
+
builder.build_editor(:content, class: 'custom-class')
|
149
|
+
|
150
|
+
expect(template).to have_received(:ckeditor5_editor)
|
151
|
+
.with(hash_including(class: 'custom-class'))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe CKEditor5::Rails::Hooks::Form::FormBuilderExtension do
|
159
|
+
let(:template) { instance_double('ActionView::Base') }
|
160
|
+
let(:object) { double('Post') }
|
161
|
+
let(:builder) do
|
162
|
+
Class.new do
|
163
|
+
include CKEditor5::Rails::Hooks::Form::FormBuilderExtension
|
164
|
+
attr_reader :object_name, :object, :template
|
165
|
+
|
166
|
+
def initialize(object_name, object, template)
|
167
|
+
@object_name = object_name
|
168
|
+
@object = object
|
169
|
+
@template = template
|
170
|
+
end
|
171
|
+
end.new('post', object, template)
|
172
|
+
end
|
173
|
+
|
174
|
+
describe '#ckeditor5' do
|
175
|
+
let(:input_builder) { instance_double(CKEditor5::Rails::Hooks::Form::EditorInputBuilder) }
|
176
|
+
|
177
|
+
before do
|
178
|
+
allow(CKEditor5::Rails::Hooks::Form::EditorInputBuilder)
|
179
|
+
.to receive(:new)
|
180
|
+
.with('post', object, template)
|
181
|
+
.and_return(input_builder)
|
182
|
+
allow(input_builder).to receive(:build_editor)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'creates EditorInputBuilder with correct parameters' do
|
186
|
+
builder.ckeditor5(:content, class: 'custom-class')
|
187
|
+
|
188
|
+
expect(CKEditor5::Rails::Hooks::Form::EditorInputBuilder)
|
189
|
+
.to have_received(:new)
|
190
|
+
.with('post', object, template)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'calls build_editor with correct parameters' do
|
194
|
+
options = { class: 'custom-class' }
|
195
|
+
builder.ckeditor5(:content, options)
|
196
|
+
|
197
|
+
expect(input_builder)
|
198
|
+
.to have_received(:build_editor)
|
199
|
+
.with(:content, options)
|
200
|
+
end
|
45
201
|
end
|
46
202
|
end
|
47
203
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe CKEditor5::Rails::Hooks::SimpleForm::CKEditor5Input do
|
6
|
+
let(:template) { instance_double('ActionView::Base') }
|
7
|
+
let(:object) { double('Post', content: 'Initial content') }
|
8
|
+
let(:object_name) { 'post' }
|
9
|
+
let(:builder) do
|
10
|
+
double('FormBuilder',
|
11
|
+
object: object,
|
12
|
+
object_name: object_name,
|
13
|
+
template: template)
|
14
|
+
end
|
15
|
+
let(:attribute_name) { :content }
|
16
|
+
let(:input_html_options) { {} }
|
17
|
+
let(:input_options) { {} }
|
18
|
+
|
19
|
+
subject(:input) do
|
20
|
+
described_class.new(builder, attribute_name, nil, input_html_options, input_options)
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
allow(template).to receive(:ckeditor5_editor)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#input' do
|
28
|
+
it 'renders ckeditor with default options' do
|
29
|
+
input.input
|
30
|
+
|
31
|
+
expect(template).to have_received(:ckeditor5_editor).with(
|
32
|
+
hash_including(
|
33
|
+
preset: :default,
|
34
|
+
type: :classic,
|
35
|
+
config: nil,
|
36
|
+
initial_data: 'Initial content',
|
37
|
+
name: 'post[content]',
|
38
|
+
class: [{}, :required] # Simple Form adds these classes by default
|
39
|
+
)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with custom options' do
|
44
|
+
let(:input_options) do
|
45
|
+
{
|
46
|
+
preset: :custom_preset,
|
47
|
+
type: :inline,
|
48
|
+
config: { toolbar: [:bold] }
|
49
|
+
}
|
50
|
+
end
|
51
|
+
let(:input_html_options) do
|
52
|
+
{ class: 'custom-class', id: 'custom-id' }
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'renders ckeditor with merged options' do
|
56
|
+
input.input
|
57
|
+
|
58
|
+
expect(template).to have_received(:ckeditor5_editor).with(
|
59
|
+
hash_including(
|
60
|
+
preset: :custom_preset,
|
61
|
+
type: :inline,
|
62
|
+
config: { toolbar: [:bold] },
|
63
|
+
initial_data: 'Initial content',
|
64
|
+
name: 'post[content]',
|
65
|
+
class: [{ class: 'custom-class', id: 'custom-id' }, :required]
|
66
|
+
)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'when object does not respond to attribute' do
|
72
|
+
let(:object) { double('Post') }
|
73
|
+
let(:input_options) { { initial_data: 'Provided content' } }
|
74
|
+
|
75
|
+
it 'uses initial_data from options' do
|
76
|
+
input.input
|
77
|
+
|
78
|
+
expect(template).to have_received(:ckeditor5_editor).with(
|
79
|
+
hash_including(initial_data: 'Provided content')
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'with wrapper options' do
|
85
|
+
let(:wrapper_options) { { wrapper_class: 'wrapper' } }
|
86
|
+
let(:input_html_options) { { class: 'input' } }
|
87
|
+
|
88
|
+
it 'merges wrapper options with input options' do
|
89
|
+
input.input(wrapper_options)
|
90
|
+
|
91
|
+
expect(template).to have_received(:ckeditor5_editor).with(
|
92
|
+
hash_including(
|
93
|
+
class: [{ class: 'input' }, :required],
|
94
|
+
wrapper_class: 'wrapper'
|
95
|
+
)
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|