diff --git a/.rubocop.yml b/.rubocop.yml index 4cec750..37280f0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,11 +2,15 @@ inherit_from: - https://relaxed.ruby.style/rubocop.yml -require: - - rubocop-packaging +plugins: + - rubocop-capybara - rubocop-performance - rubocop-rails - rubocop-rspec + - rubocop-rspec_rails + +require: + - rubocop-packaging AllCops: Exclude: diff --git a/Gemfile b/Gemfile index 85be87b..3ec6b4f 100644 --- a/Gemfile +++ b/Gemfile @@ -38,10 +38,12 @@ gem 'rspec-retry' # Linters gem 'fasterer' gem 'rubocop' +gem 'rubocop-capybara' gem 'rubocop-packaging' gem 'rubocop-performance' gem 'rubocop-rails' gem 'rubocop-rspec' +gem 'rubocop-rspec_rails' # Tools gem 'pry-rails' diff --git a/spec/page_objects/admin/authors/edit_page.rb b/spec/page_objects/admin/authors/edit_page.rb new file mode 100644 index 0000000..542ed12 --- /dev/null +++ b/spec/page_objects/admin/authors/edit_page.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative '../../base_page' + +module Admin + module Authors + class EditPage < BasePage + include Capybara::DSL + end + end +end diff --git a/spec/page_objects/admin/posts/edit_page.rb b/spec/page_objects/admin/posts/edit_page.rb new file mode 100644 index 0000000..2c2d49d --- /dev/null +++ b/spec/page_objects/admin/posts/edit_page.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative '../../base_page' + +module Admin + module Posts + class EditPage < BasePage + include Capybara::DSL + end + end +end diff --git a/spec/page_objects/base_object.rb b/spec/page_objects/base_object.rb new file mode 100644 index 0000000..1c88ce4 --- /dev/null +++ b/spec/page_objects/base_object.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class BaseObject + include Capybara::DSL + + attr_reader :element, :selector + + def initialize(selector:) + @selector = selector + @element = find(selector) + end +end diff --git a/spec/page_objects/base_page.rb b/spec/page_objects/base_page.rb new file mode 100644 index 0000000..861d7aa --- /dev/null +++ b/spec/page_objects/base_page.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class BasePage + include Capybara::DSL + + attr_reader :path + + def initialize(path:) + @path = path + end + + def load + visit(path) + end +end diff --git a/spec/page_objects/shared/html_editor.rb b/spec/page_objects/shared/html_editor.rb new file mode 100644 index 0000000..b8233ed --- /dev/null +++ b/spec/page_objects/shared/html_editor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative '../base_object' + +class HtmlEditor < BaseObject + def content_element + raise NotImplementedError + end + + def clear + select_all + content_element.send_keys(:delete) + self + end + + # @return [self] + def open_dropdown + raise NotImplementedError + end + + def select_all + content_element.send_keys([:control, "a"]) + self + end + + def toolbar_control(control, ...) + send(:"toggle_#{control}", ...) + self + end + + def <<(content) + content_element.send_keys(content) + self + end +end diff --git a/spec/page_objects/shared/quill_editor.rb b/spec/page_objects/shared/quill_editor.rb new file mode 100644 index 0000000..cc59586 --- /dev/null +++ b/spec/page_objects/shared/quill_editor.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class QuillEditor < HtmlEditor + SELECTOR = '.ql-container' + TOOLBAR_SELECTOR = '.ql-toolbar' + + attr_reader :toolbar, :toolbar_selector + + def initialize(selector: SELECTOR, toolbar_selector: TOOLBAR_SELECTOR) + super(selector: selector) + @toolbar = find(toolbar_selector) + @toolbar_selector = toolbar_selector + end + + def content_element + @content_element ||= find("#{selector} .ql-editor") + end + + def control_selector(control) + case control&.to_sym + when :bold then "#{toolbar_selector} button.ql-bold" + when :italic then "#{toolbar_selector} button.ql-italic" + when :underline then "#{toolbar_selector} button.ql-underline" + when :link then "#{toolbar_selector} button.ql-link" + else raise "Invalid control #{control}" + end + end + + def toggle_bold + find(control_selector(:bold)).click + end + + def toggle_italic + find(control_selector(:italic)).click + end + + def toggle_underline + find(control_selector(:underline)).click + end + + def toggle_link + find(control_selector(:link)).click + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 0d39be9..0af914f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -12,7 +12,8 @@ require 'capybara/rails' require 'rspec/retry' -Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require_relative f } +Rails.root.glob("../support/**/*.rb").each { |f| require_relative f } +Rails.root.glob("../page_objects/**/*.rb").each { |f| require_relative f } # Force deprecations to raise an exception. # ActiveSupport::Deprecation.behavior = :raise diff --git a/spec/system/quill_editor_spec.rb b/spec/system/quill_editor_spec.rb index 97e89e7..5d34984 100644 --- a/spec/system/quill_editor_spec.rb +++ b/spec/system/quill_editor_spec.rb @@ -1,86 +1,114 @@ # frozen_string_literal: true RSpec.describe 'Quill editor' do + def lookup_editor(field:) + selector = ["##{field}_input.quill_editor", QuillEditor::SELECTOR].join(' ') + toolbar_selector = ["##{field}_input.quill_editor", QuillEditor::TOOLBAR_SELECTOR].join(' ') + QuillEditor.new(selector: selector, toolbar_selector: toolbar_selector) + end + let(:author) { Author.create!(email: 'some_email@example.com', name: 'John Doe', age: 30) } - let!(:post) { Post.create!(title: 'Test', author: author, description: 'Some content...') } + let!(:post) { Post.create!(title: 'Test', author: author, description: '') } context 'with a Quill editor' do - it 'initialize the editor' do - visit "/admin/posts/#{post.id}/edit" + let(:editor) { lookup_editor(field: 'post_description') } - %w[bold italic underline link].each do |button| - expect(page).to have_css(".ql-toolbar button.ql-#{button}") - end - expect(page).to have_css('#post_description[data-aa-quill-editor]') - expect(page).to have_css('#post_description_input .ql-editor', text: 'Some content...') + before do + path = edit_admin_post_path(post) + Admin::Posts::EditPage.new(path: path).load end - it 'adds some text to the description' do - visit "/admin/posts/#{post.id}/edit" + it 'adds some text to the description', :aggregate_failures do + expect(page).to have_css('#post_description[data-aa-quill-editor]') + editor << 'Some content...' + %i[bold italic underline link].each do |control| + expect(page).to have_css editor.control_selector(control) + end + expect(editor.content_element).to have_content('Some content...') + expect { find('[type="submit"]').click } + .to change { post.reload.description }.to '

Some content...

' + end - find('#post_description_input .ql-editor').click - find('#post_description_input .ql-editor').base.send_keys('more text') - find('[type="submit"]').click + it 'adds some bold text to the description', :aggregate_failures do + editor.toolbar_control(:bold) + editor << 'Some bold text' - expect(page).to have_content('was successfully updated') - expect(post.reload.description).to eq '

Some content...more text

' + expect(editor.content_element).to have_content('Some bold text') + expect { find('[type="submit"]').click } + .to change { post.reload.description }.to '

Some bold text

' end - it 'adds some bold text to the description' do - visit "/admin/posts/#{post.id}/edit" - - find('#post_description_input .ql-editor').click - find('#post_description_input .ql-toolbar .ql-bold').click - find('#post_description_input .ql-editor').base.send_keys('more text') - find('[type="submit"]').click + it 'adds some italic text to the description', :aggregate_failures do + editor.toolbar_control(:italic) + editor << 'Some italic text' - expect(post.reload.description).to eq '

Some content...more text

' + expect(editor.content_element).to have_content('Some italic text') + expect { find('[type="submit"]').click } + .to change { post.reload.description }.to '

Some italic text

' end - it 'adds some italic text to the description' do - visit "/admin/posts/#{post.id}/edit" + it 'adds some underline text to the description', :aggregate_failures do + editor.toolbar_control(:underline) + editor << 'Some underline text' - find('#post_description_input .ql-editor').click - find('#post_description_input .ql-toolbar .ql-italic').click - find('#post_description_input .ql-editor').base.send_keys('more text') - find('[type="submit"]').click + expect(editor.content_element).to have_content('Some underline text') + expect { find('[type="submit"]').click } + .to change { post.reload.description }.to '

Some underline text

' + end - expect(post.reload.description).to eq '

Some content...more text

' + it 'adds a link to the description', :aggregate_failures do + editor << "Just a link" + editor.select_all + editor.toolbar_control(:link) + editor.element.find('[data-link]').send_keys("https://www.google.com", :return) + + expect(editor.content_element).to have_content('Just a link') + html = '

Just a link

' + expect { find('[type="submit"]').click }.to change { post.reload.description }.to html end end context 'with 2 Quill editors' do - it 'updates some HTML content for 2 fields' do - visit "/admin/posts/#{post.id}/edit" - - find('#post_description_input .ql-editor').click - find('#post_description_input .ql-toolbar .ql-bold').click - find('#post_description_input .ql-editor').base.send_keys('more text') - find('#post_summary_input .ql-editor').click - find('#post_summary_input .ql-toolbar .ql-italic').click - find('#post_summary_input .ql-editor').base.send_keys('Summary text') + before do + path = edit_admin_post_path(post) + Admin::Posts::EditPage.new(path: path).load + end + + it 'updates some HTML content for 2 fields', :aggregate_failures do + editor1 = lookup_editor(field: 'post_description') + editor1.clear.toolbar_control(:bold) + editor1 << "Some description" + + editor2 = lookup_editor(field: 'post_summary') + editor2.clear.toolbar_control(:italic) + editor2 << "Some summary" + find('[type="submit"]').click post.reload - expect(post.description).to eq '

Some content...more text

' - expect(post.summary).to eq '

Summary text

' + expect(post.description).to eq '

Some description

' + expect(post.summary).to eq '

Some summary

' end end context 'with a Quill editor in a nested resource' do - it 'updates some HTML content of a new nested resource' do - visit "/admin/authors/#{author.id}/edit" + before do + path = edit_admin_author_path(author) + Admin::Authors::EditPage.new(path: path).load + end - expect(page).to have_css('.posts.has_many_container .ql-editor', text: 'Some content...') + it 'updates some HTML content of a new nested resource', :aggregate_failures do find('.posts.has_many_container .has_many_add').click expect(page).to have_css('.posts.has_many_container .ql-editor', count: 2) + editor = lookup_editor(field: 'author_posts_attributes_1_description') + editor << "Some post text" + fill_in('author[posts_attributes][1][title]', with: 'A new post') - find('#author_posts_attributes_1_description_input .ql-editor').base.send_keys('new post text') find('[type="submit"]').click expect(page).to have_content('was successfully updated') - expect(author.posts.last.description).to eq '

new post text

' + expect(author.posts.last.description).to eq '

Some post text

' end end end