From 6d2a5de4dd2a33d73e07a98b3eef5b98493d8edf Mon Sep 17 00:00:00 2001 From: Mattia Roccoberton Date: Sun, 13 Apr 2025 12:13:07 +0200 Subject: [PATCH] test: Improve tests using page objects --- .rubocop.yml | 2 +- Gemfile | 1 - spec/dummy/app/admin/posts.rb | 6 +- spec/page_objects/admin/authors/edit_page.rb | 9 ++ spec/page_objects/admin/posts/edit_page.rb | 9 ++ spec/page_objects/base_object.rb | 12 ++ spec/page_objects/base_page.rb | 17 +++ spec/page_objects/shared/html_editor.rb | 35 +++++ spec/page_objects/shared/trumbowyg_editor.rb | 26 ++++ spec/rails_helper.rb | 8 +- spec/support/string_clean_multiline.rb | 10 ++ spec/system/trumbowyg_editor_spec.rb | 131 +++++++++++++------ 12 files changed, 221 insertions(+), 45 deletions(-) create mode 100644 spec/page_objects/admin/authors/edit_page.rb create mode 100644 spec/page_objects/admin/posts/edit_page.rb create mode 100644 spec/page_objects/base_object.rb create mode 100644 spec/page_objects/base_page.rb create mode 100644 spec/page_objects/shared/html_editor.rb create mode 100644 spec/page_objects/shared/trumbowyg_editor.rb create mode 100644 spec/support/string_clean_multiline.rb diff --git a/.rubocop.yml b/.rubocop.yml index 549dab1..7b6f26e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,7 +21,7 @@ AllCops: RSpec/ExampleLength: # default 5 - Max: 10 + Max: 12 RSpec/MultipleMemoizedHelpers: # default: 5 diff --git a/Gemfile b/Gemfile index bb4b1b8..4b914fd 100644 --- a/Gemfile +++ b/Gemfile @@ -46,7 +46,6 @@ gem 'capybara' gem 'cuprite' gem 'rspec_junit_formatter' gem 'rspec-rails' -gem 'rspec-retry' gem 'simplecov', require: false # Linters diff --git a/spec/dummy/app/admin/posts.rb b/spec/dummy/app/admin/posts.rb index 718824f..3a36042 100644 --- a/spec/dummy/app/admin/posts.rb +++ b/spec/dummy/app/admin/posts.rb @@ -33,13 +33,13 @@ end form do |f| - buttons = %w[bold italic underline link justifyRight] + buttons = %w[strong em underline link justifyRight] f.inputs 'Post' do f.input :author f.input :title - f.input :summary, as: :trumbowyg, input_html: { data: { options: { btns: buttons } } } - f.input :description, as: :trumbowyg # using default options + f.input :summary, as: :trumbowyg # using default options + f.input :description, as: :trumbowyg, input_html: { data: { options: { btns: buttons } } } f.input :category f.input :dt f.input :position 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..5564340 --- /dev/null +++ b/spec/page_objects/admin/authors/edit_page.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +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..f42c47b --- /dev/null +++ b/spec/page_objects/admin/posts/edit_page.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +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..1568c32 --- /dev/null +++ b/spec/page_objects/base_page.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class BasePage + include Capybara::DSL + + attr_reader :path + + def initialize(path:) + @path = path + end + + def load = visit(path) + + def lookup_editor(editor_container:) + @editor = Shared::TrumbowygEditor.new(editor_container) + 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..d74b934 --- /dev/null +++ b/spec/page_objects/shared/html_editor.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Shared + 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 +end diff --git a/spec/page_objects/shared/trumbowyg_editor.rb b/spec/page_objects/shared/trumbowyg_editor.rb new file mode 100644 index 0000000..d1a041b --- /dev/null +++ b/spec/page_objects/shared/trumbowyg_editor.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Shared + class TrumbowygEditor < HtmlEditor + attr_reader :editor_selector + + def initialize(container) + @editor_selector = "#{container} .trumbowyg-box" + super(selector: editor_selector) + end + + def content = content_element['innerHTML'] + + def content_element + @content_element ||= find("#{editor_selector} [contenteditable]") + end + + def toggle_bold = find("#{editor_selector} .trumbowyg-strong-button").click + + def toggle_delete = find("#{editor_selector} .trumbowyg-del-button").click + + def toggle_italic = find("#{editor_selector} .trumbowyg-em-button").click + + def toggle_underline = find("#{editor_selector} .trumbowyg-underline-button").click + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 43aec33..e197eee 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -2,6 +2,11 @@ require_relative 'spec_helper' +require 'zeitwerk' +loader = Zeitwerk::Loader.new +loader.push_dir("#{__dir__}/page_objects") +loader.setup + ENV['RAILS_ENV'] = 'test' require File.expand_path('dummy/config/environment.rb', __dir__) @@ -10,7 +15,6 @@ require 'rspec/rails' require 'capybara/rails' -require 'rspec/retry' Dir[File.expand_path('support/**/*.rb', __dir__)].each { |f| require_relative f } @@ -37,8 +41,6 @@ config.use_instantiated_fixtures = false config.render_views = false - config.default_retry_count = 2 - config.before(:suite) do intro = ('-' * 80) intro << "\n" diff --git a/spec/support/string_clean_multiline.rb b/spec/support/string_clean_multiline.rb new file mode 100644 index 0000000..16fc0db --- /dev/null +++ b/spec/support/string_clean_multiline.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module StringCleanMultiline + refine String do + def clean_multiline + # Get rid of newlines and indentation spaces + strip.gsub(/\s*\n\s*/, "") + end + end +end diff --git a/spec/system/trumbowyg_editor_spec.rb b/spec/system/trumbowyg_editor_spec.rb index 012c012..22d0c9c 100644 --- a/spec/system/trumbowyg_editor_spec.rb +++ b/spec/system/trumbowyg_editor_spec.rb @@ -1,66 +1,123 @@ # frozen_string_literal: true +using StringCleanMultiline + RSpec.describe 'Trumbowyg editor' do let(:author) { Author.create!(email: 'some_email@example.com', name: 'John Doe', age: 30) } let(:post) do - Post.create!(title: 'Test', author: author, summary: 'Some content!', description: 'Some content...') - end - - before do - post + Post.create!(title: 'Test', author: author, description: '

Some content

', summary: '

Post summary

') end - after do - Post.delete_all - author.delete - end + let(:submit_button) { find('#post_submit_action [type="submit"]') } context 'with a Trumbowyg editor' do - it 'initialize the editor', :aggregate_failures do - visit "/admin/posts/#{post.id}/edit" - - %w[bold italic underline link justifyRight].each do |button| - expect(page).to have_css(".trumbowyg button.trumbowyg-#{button}-button") - end - expect(page).to have_css('#post_description[data-aa-trumbowyg]', visible: :hidden) - expect(page).to have_css('#post_description_input .trumbowyg-editor', text: 'Some content...') + let(:edit_page) do + path = edit_admin_post_path(post) + Admin::Posts::EditPage.new(path: path) end + let(:editor) { edit_page.lookup_editor(editor_container: '#post_description_input') } + let(:input_field) { find('#post_description[data-aa-trumbowyg]', visible: :hidden) } - it 'adds some text to the summary', :aggregate_failures do - visit "/admin/posts/#{post.id}/edit" + before do + edit_page.load + end - find('#post_summary_input .trumbowyg-editor').base.send_keys('More text') - find('[type="submit"]').click + it 'initializes the editor', :aggregate_failures do + expect(editor.content_element).to be_present + expect(editor.content).to eq('

Some content

') + expect(input_field.value).to eq('

Some content

') + end - expect(page).to have_content('was successfully updated') - expect(post.reload.summary).to eq '

Some content!More text

' + it 'edits some content using the editor' do + editor << :return << 'More content' + editor.toggle_bold + editor << 'Some bold' + editor.toggle_italic + editor << 'Some italic' + editor.toggle_underline + editor << 'Some underline' + + expect(editor.content).to eq <<~HTML.clean_multiline +

Some content

+

More contentSome boldSome italicSome underline

+ HTML end - it 'adds right aligned text to the summary', :aggregate_failures do - visit "/admin/posts/#{post.id}/edit" + it 'updates the field when submitting', :aggregate_failures do + editor.toggle_bold + editor << 'More content' - find('#post_summary_input .trumbowyg-button-pane .trumbowyg-justifyRight-button').click - find('[type="submit"]').click + before = '

Some content

' + after = '

More contentSome content

' + expect { submit_button.click }.to change { post.reload.description }.from(before).to(after) expect(page).to have_content('was successfully updated') - expect(post.reload.summary).to match /text-align: right.*Some content/ + end + end + + context 'with 2 Trumbowyg editors' do + let(:edit_page) do + path = edit_admin_post_path(post) + Admin::Posts::EditPage.new(path: path) + end + let(:first_editor) { edit_page.lookup_editor(editor_container: '#post_description_input') } + let(:second_editor) { edit_page.lookup_editor(editor_container: '#post_summary_input') } + let(:first_field) { find('#post_description[data-aa-trumbowyg]', visible: :hidden) } + let(:second_field) { find('#post_summary[data-aa-trumbowyg]', visible: :hidden) } + + before do + edit_page.load + end + + it 'updates some HTML content for 2 fields', :aggregate_failures do + # Check the initial states + expect(first_editor.content).to eq('

Some content

') + expect(second_editor.content).to eq('

Post summary

') + + expect(first_field.value).to eq('

Some content

') + expect(second_field.value).to eq('

Post summary

') + + # Add some content to both the editors + first_editor.toggle_bold + first_editor << 'Some bold' + + second_editor.toggle_italic + second_editor << 'Some italic' + + # Check that both the fields change + before = '

Some content

' + after = '

Some boldSome content

' + expect { submit_button.click }.to change { post.reload.description }.from(before).to(after) + + expect(post.summary).to eq '

Some italicPost summary

' end end context 'with a Trumbowyg editor in a nested resource' do + let(:edit_page) do + path = edit_admin_author_path(author) + Admin::Authors::EditPage.new(path: path) + end + let(:submit_button) { find('#author_submit_action [type="submit"]') } + + before do + post + edit_page.load + end + it 'updates some HTML content of a new nested resource', :aggregate_failures do - visit "/admin/authors/#{author.id}/edit" + click_on 'Add New Post' - expect(page).to have_css('.posts.has_many_container .trumbowyg-box', text: 'Some content...') - find('.posts.has_many_container .has_many_add').click - expect(page).to have_css('.posts.has_many_container .trumbowyg-box', count: 2) + first_editor = edit_page.lookup_editor(editor_container: '#author_posts_attributes_0_description_input') + expect(first_editor.content).to eq('

Some content

') - fill_in('author[posts_attributes][1][title]', with: 'A new post') - find('#author_posts_attributes_1_description_input .trumbowyg-editor').base.send_keys('new post text') - find('[type="submit"]').click + fill_in('author[posts_attributes][1][title]', with: 'Some title') + second_editor = edit_page.lookup_editor(editor_container: '#author_posts_attributes_1_description_input') + second_editor.toggle_delete + second_editor << 'Some deleted' - expect(page).to have_content('was successfully updated') - expect(author.posts.last.description).to eq '

new post text

' + expect { submit_button.click }.to change(Post, :count).by(1) + expect(Post.last.description).to eq '

Some deleted

' end end end