diff --git a/.rubocop_todo/lint/ambiguous_regexp_literal.yml b/.rubocop_todo/lint/ambiguous_regexp_literal.yml index 4af24464e8eebf09a9c040b220bc8234e1b75f97..83000e7955fdf2cc171e4f8b48c5efcd3693783b 100644 --- a/.rubocop_todo/lint/ambiguous_regexp_literal.yml +++ b/.rubocop_todo/lint/ambiguous_regexp_literal.yml @@ -82,7 +82,6 @@ Lint/AmbiguousRegexpLiteral: - 'spec/services/loose_foreign_keys/cleaner_service_spec.rb' - 'spec/services/snippets/repository_validation_service_spec.rb' - 'spec/services/system_notes/merge_requests_service_spec.rb' - - 'spec/support/shared_examples/features/content_editor_shared_examples.rb' - 'spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb' - 'spec/support_specs/database/multiple_databases_helpers_spec.rb' - 'spec/tasks/gitlab/gitaly_rake_spec.rb' diff --git a/ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb b/ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb index 163a9da84bbd1b270ab7f70dca6f40df031d4c0e..e62f82f28a7a81e9c153929b5815ea5cee783e4a 100644 --- a/ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb +++ b/ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' RSpec.describe 'User adds a merge request to a merge train', :js, feature_category: :merge_trains do - include ContentEditorHelpers - let_it_be_with_refind(:project) { create(:project, :repository) } let(:user) { project.owner } diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb index d27f3ffebe6f13d162b964f2f17efbee3c902748..766a8bc91e125dce8a7a0ed075a40c68d011f54b 100644 --- a/spec/features/issues/user_comments_on_issue_spec.rb +++ b/spec/features/issues/user_comments_on_issue_spec.rb @@ -33,9 +33,7 @@ end end - # do not test quick actions here since guest users don't have permission - # to execute all quick actions - it_behaves_like 'edits content using the content editor', { with_quick_actions: false } + it_behaves_like 'rich text editor - common' it "adds comment with code block" do code_block_content = "Command [1]: /usr/local/bin/git , see [text](doc/text)" diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 01a14aaca75c2b868551f2717d4f4607a3f43bbc..09114ba4b80ab6485af697bc868b8218de3c5f86 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -318,7 +318,13 @@ visit(new_project_issue_path(project)) end - it_behaves_like 'edits content using the content editor' + it_behaves_like 'rich text editor - autocomplete' + it_behaves_like 'rich text editor - code blocks' + it_behaves_like 'rich text editor - common' + it_behaves_like 'rich text editor - copy/paste' + it_behaves_like 'rich text editor - links' + it_behaves_like 'rich text editor - media' + it_behaves_like 'rich text editor - selection' end context "when signed in as user with special characters in their name" do diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb index b3ac374ae6307b41f231b6c4bd5c9376a1b17abe..53fe7a9fd24f85b3a2f116a4c2483a37078a7b78 100644 --- a/spec/features/issues/user_edits_issue_spec.rb +++ b/spec/features/issues/user_edits_issue_spec.rb @@ -26,7 +26,7 @@ visit edit_project_issue_path(project, issue) end - it_behaves_like 'edits content using the content editor' + it_behaves_like 'rich text editor - common' it "previews content", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391757' do form = first(".gfm-form") diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb index d02463efedd1be7ba412bf8ddd3c52b4922cc8a5..25afa40d64f447e677007c4ae68e3a89a765933d 100644 --- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb +++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb @@ -33,9 +33,7 @@ end end - context 'with content editor' do - it_behaves_like 'edits content using the content editor' - end + it_behaves_like 'rich text editor - common' it 'replys to a new comment' do page.within('.js-main-target-form') do diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb index 584a17ae33d6adb5c23fa8bd76d62bfa4f5b3214..180ec5cc4ad61a6f9cc5fe76d52c9a8ed905e390 100644 --- a/spec/features/merge_request/user_edits_merge_request_spec.rb +++ b/spec/features/merge_request/user_edits_merge_request_spec.rb @@ -109,5 +109,5 @@ end end - it_behaves_like 'edits content using the content editor' + it_behaves_like 'rich text editor - common' end diff --git a/spec/support/helpers/content_editor_helpers.rb b/spec/support/helpers/rich_text_editor_helpers.rb similarity index 90% rename from spec/support/helpers/content_editor_helpers.rb rename to spec/support/helpers/rich_text_editor_helpers.rb index 7e7ecc197fc1a3502727c4d053da8501fab8b451..38f6bbd01a2e300b31efc8d212c0685717d51b34 100644 --- a/spec/support/helpers/content_editor_helpers.rb +++ b/spec/support/helpers/rich_text_editor_helpers.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true -module ContentEditorHelpers +module RichTextEditorHelpers + def content_editor_testid + '[data-testid="content-editor"] [contenteditable].ProseMirror' + end + def switch_to_markdown_editor click_button("Switch to plain text editing") end diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb deleted file mode 100644 index d0b2d0c9cae19e140a7be262e3f6245570e63881..0000000000000000000000000000000000000000 --- a/spec/support/shared_examples/features/content_editor_shared_examples.rb +++ /dev/null @@ -1,792 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.shared_examples 'edits content using the content editor' do |params = { - with_expanded_references: true, - with_quick_actions: true -}| - include ContentEditorHelpers - - let(:content_editor_testid) { '[data-testid="content-editor"] [contenteditable].ProseMirror' } - - let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') } - let(:modifier_key) { is_mac ? :command : :control } - - it 'saves page content in local storage if the user navigates away' do - switch_to_content_editor - - expect(page).to have_css(content_editor_testid) - - type_in_content_editor ' Typing text in the content editor' - - wait_until_hidden_field_is_updated /Typing text in the content editor/ - - begin - refresh - rescue Selenium::WebDriver::Error::UnexpectedAlertOpenError - end - - expect(page).to have_text('Typing text in the content editor') - end - - it 'autofocuses the rich text editor when switching to rich text' do - switch_to_content_editor - - expect(page).to have_css("#{content_editor_testid}:focus") - end - - it 'autofocuses the plain text editor when switching back to markdown' do - switch_to_content_editor - switch_to_markdown_editor - - expect(page).to have_css("textarea:focus") - end - - describe 'creating and editing links' do - before do - switch_to_content_editor - end - - context 'when clicking the link icon in the toolbar' do - it 'shows the link bubble menu' do - page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click - - expect(page).to have_css('[data-testid="link-bubble-menu"]') - end - - context 'if no text is selected' do - before do - page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click - end - - it 'opens an empty inline modal to create a link' do - page.within '[data-testid="link-bubble-menu"]' do - expect(page).to have_field('link-text', with: '') - expect(page).to have_field('link-href', with: '') - end - end - - context 'when the user clicks the apply button' do - it 'applies the changes to the document' do - page.within '[data-testid="link-bubble-menu"]' do - fill_in 'link-text', with: 'Link to GitLab home page' - fill_in 'link-href', with: 'https://gitlab.com' - - click_button 'Apply' - end - - page.within content_editor_testid do - expect(page).to have_css('a[href="https://gitlab.com"]') - expect(page).to have_text('Link to GitLab home page') - end - end - end - - context 'when the user clicks the cancel button' do - it 'does not apply the changes to the document' do - page.within '[data-testid="link-bubble-menu"]' do - fill_in 'link-text', with: 'Link to GitLab home page' - fill_in 'link-href', with: 'https://gitlab.com' - - click_button 'Cancel' - end - - page.within content_editor_testid do - expect(page).not_to have_css('a') - end - end - end - end - - context 'if text is selected' do - before do - type_in_content_editor 'The quick brown fox jumps over the lazy dog' - type_in_content_editor [:shift, :left] - type_in_content_editor [:shift, :left] - type_in_content_editor [:shift, :left] - - page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click - end - - it 'prefills inline modal to create a link' do - page.within '[data-testid="link-bubble-menu"]' do - expect(page).to have_field('link-text', with: 'dog') - expect(page).to have_field('link-href', with: '') - end - end - - context 'when the user clicks the apply button' do - it 'applies the changes to the document' do - page.within '[data-testid="link-bubble-menu"]' do - fill_in 'link-text', with: 'new dog' - fill_in 'link-href', with: 'https://en.wikipedia.org/wiki/Shiba_Inu' - - click_button 'Apply' - end - - page.within content_editor_testid do - expect(page).to have_selector('a[href="https://en.wikipedia.org/wiki/Shiba_Inu"]', - text: 'new dog' - ) - end - end - end - end - end - - context 'if cursor is placed on an existing link' do - before do - type_in_content_editor 'Link to [GitLab home **page**](https://gitlab.com)' - type_in_content_editor :left - end - - it 'prefills inline modal to edit the link' do - page.within '[data-testid="link-bubble-menu"]' do - page.find('[data-testid="edit-link"]').click - - expect(page).to have_field('link-text', with: 'GitLab home page') - expect(page).to have_field('link-href', with: 'https://gitlab.com') - end - end - - it 'updates the link attributes if text is not updated' do - page.within '[data-testid="link-bubble-menu"]' do - page.find('[data-testid="edit-link"]').click - - fill_in 'link-href', with: 'https://about.gitlab.com' - - click_button 'Apply' - end - - page.within content_editor_testid do - expect(page).to have_selector('a[href="https://about.gitlab.com"]') - expect(page.find('a')).to have_text('GitLab home page') - expect(page).to have_selector('strong', text: 'page') - end - end - - it 'updates the link attributes and text if text is updated' do - page.within '[data-testid="link-bubble-menu"]' do - page.find('[data-testid="edit-link"]').click - - fill_in 'link-text', with: 'GitLab about page' - fill_in 'link-href', with: 'https://about.gitlab.com' - - click_button 'Apply' - end - - page.within content_editor_testid do - expect(page).to have_selector('a[href="https://about.gitlab.com"]', - text: 'GitLab about page' - ) - expect(page).not_to have_selector('strong') - end - end - - it 'does nothing if Cancel is clicked' do - page.within '[data-testid="link-bubble-menu"]' do - page.find('[data-testid="edit-link"]').click - - click_button 'Cancel' - end - - page.within content_editor_testid do - expect(page).to have_selector('a[href="https://gitlab.com"]', - text: 'GitLab home page' - ) - expect(page).to have_selector('strong') - end - end - - context 'when the user clicks the unlink button' do - it 'removes the link' do - page.within '[data-testid="link-bubble-menu"]' do - page.find('[data-testid="remove-link"]').click - end - - page.within content_editor_testid do - expect(page).not_to have_selector('a') - expect(page).to have_selector('strong', text: 'page') - end - end - end - end - - context 'when selection spans more than a link' do - before do - type_in_content_editor 'a [b **c**](https://gitlab.com)' - - type_in_content_editor [:shift, :left] - type_in_content_editor [:shift, :left] - type_in_content_editor [:shift, :left] - type_in_content_editor [:shift, :left] - type_in_content_editor [:shift, :left] - - page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click - end - - it 'prefills inline modal with the entire selection' do - page.within '[data-testid="link-bubble-menu"]' do - expect(page).to have_field('link-text', with: 'a b c') - expect(page).to have_field('link-href', with: '') - end - end - - it 'expands the link and updates the link attributes if text is not updated' do - page.within '[data-testid="link-bubble-menu"]' do - fill_in 'link-href', with: 'https://about.gitlab.com' - - click_button 'Apply' - end - - page.within content_editor_testid do - expect(page).to have_selector('a[href="https://about.gitlab.com"]') - expect(page.find('a')).to have_text('a b c') - expect(page).to have_selector('strong', text: 'c') - end - end - - it 'expands the link, updates the link attributes and text if text is updated', - quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/419684' do - page.within '[data-testid="link-bubble-menu"]' do - fill_in 'link-text', with: 'new text' - fill_in 'link-href', with: 'https://about.gitlab.com' - - click_button 'Apply' - end - - page.within content_editor_testid do - expect(page).to have_selector('a[href="https://about.gitlab.com"]', - text: 'new text' - ) - expect(page).not_to have_selector('strong') - end - end - end - end - - describe 'selecting text' do - before do - switch_to_content_editor - - # delete all text first - type_in_content_editor [modifier_key, 'a'] - type_in_content_editor :backspace - - type_in_content_editor 'The quick **brown** fox _jumps_ over the lazy dog!' - type_in_content_editor :enter - type_in_content_editor '[Link](https://gitlab.com)' - type_in_content_editor :enter - type_in_content_editor 'Jackdaws love my ~~big~~ sphinx of quartz!' - - # select all text - type_in_content_editor [modifier_key, 'a'] - end - - it 'renders selected text in a .content-editor-selection class' do - page.within content_editor_testid do - assert_selected 'The quick' - assert_selected 'brown' - assert_selected 'fox' - assert_selected 'jumps' - assert_selected 'over the lazy dog!' - - assert_selected 'Link' - - assert_selected 'Jackdaws love my' - assert_selected 'big' - assert_selected 'sphinx of quartz!' - end - end - - def assert_selected(text) - expect(page).to have_selector('.content-editor-selection', text: text) - end - end - - describe 'media elements bubble menu' do - before do - switch_to_content_editor - - click_attachment_button - end - - it 'displays correct media bubble menu for images', :js do - display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'dk.png' - - expect_media_bubble_menu_to_be_visible - end - - it 'displays correct media bubble menu for video', :js do - display_media_bubble_menu '[data-testid="content_editor_editablebox"] video', 'video_sample.mp4' - - expect_media_bubble_menu_to_be_visible - end - end - - describe 'code block' do - before do - visit(profile_preferences_path) - - find('.syntax-theme').choose('Dark') - - wait_for_requests - - page.go_back - refresh - switch_to_content_editor - end - - it 'applies theme classes to code blocks' do - expect(page).not_to have_css('.content-editor-code-block.code.highlight.dark') - - type_in_content_editor [:enter, :enter] - type_in_content_editor '```js ' # trigger input rule - type_in_content_editor 'var a = 0' - - expect(page).to have_css('.content-editor-code-block.code.highlight.dark') - end - end - - describe 'code block bubble menu' do - before do - switch_to_content_editor - end - - it 'shows a code block bubble menu for a code block' do - type_in_content_editor [:enter, :enter] - - type_in_content_editor '```js ' # trigger input rule - type_in_content_editor 'var a = 0' - type_in_content_editor [:shift, :left] - - expect(page).to have_css('[data-testid="code-block-bubble-menu"]') - end - - it 'sets code block type to "javascript" for `js`' do - type_in_content_editor [:enter, :enter] - - type_in_content_editor '```js ' - type_in_content_editor 'var a = 0' - - expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Javascript') - end - - it 'sets code block type to "Custom (nomnoml)" for `nomnoml`' do - type_in_content_editor [:enter, :enter] - - type_in_content_editor '```nomnoml ' - type_in_content_editor 'test' - - expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Custom (nomnoml)') - end - end - - describe 'mermaid diagram' do - before do - switch_to_content_editor - - type_in_content_editor [:enter, :enter] - type_in_content_editor '```mermaid ' - type_in_content_editor ['graph TD;', :enter, ' JohnDoe12 --> HelloWorld34'] - end - - it 'renders and updates the diagram correctly in a sandboxed iframe' do - iframe = find(content_editor_testid).find('iframe') - expect(iframe['src']).to include('/-/sandbox/mermaid') - - within_frame(iframe) do - expect(find('svg .nodes').text).to include('JohnDoe12') - expect(find('svg .nodes').text).to include('HelloWorld34') - end - - expect(iframe['height'].to_i).to be > 100 - - find(content_editor_testid).send_keys [:enter, ' JaneDoe34 --> HelloWorld56'] - - within_frame(iframe) do - page.has_content?('JaneDoe34') - - expect(find('svg .nodes').text).to include('JaneDoe34') - expect(find('svg .nodes').text).to include('HelloWorld56') - end - end - - it 'toggles the diagram when preview button is clicked', - quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/397682' do - find('[data-testid="preview-diagram"]').click - - expect(find(content_editor_testid)).not_to have_selector('iframe') - - find('[data-testid="preview-diagram"]').click - - iframe = find(content_editor_testid).find('iframe') - - within_frame(iframe) do - expect(find('svg .nodes').text).to include('JohnDoe12') - expect(find('svg .nodes').text).to include('HelloWorld34') - end - end - end - - describe 'rendering with initial content' do - it 'renders correctly with table as initial content' do - textarea = find 'textarea' - textarea.send_keys "\n\n" - textarea.send_keys "| First Header | Second Header |\n" - textarea.send_keys "|--------------|---------------|\n" - textarea.send_keys "| Content from cell 1 | Content from cell 2 |\n\n" - textarea.send_keys "Content below table" - - switch_to_content_editor - - expect(page).not_to have_text('An error occurred') - end - end - - describe 'pasting text' do - before do - switch_to_content_editor - - type_in_content_editor [modifier_key, 'a'] - type_in_content_editor :delete - - type_in_content_editor "Some **rich** _text_ ~~content~~ [link](https://gitlab.com)" - - type_in_content_editor [modifier_key, 'a'] - type_in_content_editor [modifier_key, 'x'] - end - - it 'pastes text with formatting if ctrl + v is pressed' do - type_in_content_editor [modifier_key, 'v'] - - page.within content_editor_testid do - expect(page).to have_selector('strong', text: 'rich') - expect(page).to have_selector('em', text: 'text') - expect(page).to have_selector('s', text: 'content') - expect(page).to have_selector('a[href="https://gitlab.com"]', text: 'link') - end - end - - it 'does not show a loading indicator after undo paste' do - type_in_content_editor [modifier_key, 'v'] - type_in_content_editor [modifier_key, 'z'] - - page.within content_editor_testid do - expect(page).not_to have_css('.gl-dots-loader') - end - end - - it 'pastes raw text without formatting if shift + ctrl + v is pressed' do - type_in_content_editor [modifier_key, :shift, 'v'] - - page.within content_editor_testid do - expect(page).to have_text('Some rich text content link') - - expect(page).not_to have_selector('strong') - expect(page).not_to have_selector('em') - expect(page).not_to have_selector('s') - expect(page).not_to have_selector('a') - end - end - - it 'pastes raw markdown with formatting when pasting inside a markdown code block' do - type_in_content_editor '```md' - type_in_content_editor :enter - type_in_content_editor [modifier_key, 'v'] - - page.within content_editor_testid do - expect(page).to have_selector('pre', text: 'Some **rich** _text_ ~~content~~ [link](https://gitlab.com)') - end - end - - it 'pastes raw markdown without formatting when pasting inside a plaintext code block' do - type_in_content_editor '```' - type_in_content_editor :enter - type_in_content_editor [modifier_key, 'v'] - - page.within content_editor_testid do - expect(page).to have_selector('pre', text: 'Some rich text content link') - end - end - - it 'pastes raw text without formatting, stripping whitespaces, if shift + ctrl + v is pressed' do - type_in_content_editor " Some **rich**" - type_in_content_editor :enter - type_in_content_editor " _text_" - type_in_content_editor :enter - type_in_content_editor " ~~content~~" - type_in_content_editor :enter - type_in_content_editor " [link](https://gitlab.com)" - - type_in_content_editor [modifier_key, 'a'] - type_in_content_editor [modifier_key, 'x'] - type_in_content_editor [modifier_key, :shift, 'v'] - - page.within content_editor_testid do - expect(page).to have_text('Some rich text content link') - end - end - end - - describe 'autocomplete suggestions' do - let(:suggestions_dropdown) { '[data-testid="content-editor-suggestions-dropdown"]' } - - before do - if defined?(project) - create(:issue, project: project, title: 'My Cool Linked Issue') - create(:merge_request, source_project: project, source_branch: 'branch-1', title: 'My Cool Merge Request') - create(:label, project: project, title: 'My Cool Label') - create(:milestone, project: project, title: 'My Cool Milestone') - - project.add_maintainer(create(:user, name: 'abc123', username: 'abc123')) - else # group wikis - project = create(:project, group: group) - - create(:issue, project: project, title: 'My Cool Linked Issue') - create(:merge_request, source_project: project, source_branch: 'branch-1', title: 'My Cool Merge Request') - create(:group_label, group: group, title: 'My Cool Label') - create(:milestone, group: group, title: 'My Cool Milestone') - - project.add_maintainer(create(:user, name: 'abc123', username: 'abc123')) - end - - switch_to_content_editor - - type_in_content_editor :enter - - stub_feature_flags(disable_all_mention: false) - end - - if params[:with_expanded_references] - describe 'when expanding an issue reference' do - it 'displays full reference name' do - new_issue = create(:issue, project: project, title: 'Brand New Issue') - - type_in_content_editor "##{new_issue.iid}+s " - - expect(page).to have_text('Brand New Issue') - end - end - - describe 'when expanding an MR reference' do - it 'displays full reference name' do - new_mr = create(:merge_request, source_project: project, source_branch: 'branch-2', title: 'Brand New MR') - - type_in_content_editor "!#{new_mr.iid}+s " - - expect(page).to have_text('Brand New') - end - end - end - - if params[:with_quick_actions] - it 'shows suggestions for quick actions' do - type_in_content_editor '/a' - - expect(find(suggestions_dropdown)).to have_text('/assign') - expect(find(suggestions_dropdown)).to have_text('/label') - end - - it 'adds the correct prefix for /assign' do - type_in_content_editor '/assign' - - expect(find(suggestions_dropdown)).to have_text('/assign') - send_keys :enter - - expect(page).to have_text('/assign @') - end - - it 'adds the correct prefix for /label' do - type_in_content_editor '/label' - - expect(find(suggestions_dropdown)).to have_text('/label') - send_keys :enter - - expect(page).to have_text('/label ~') - end - - it 'adds the correct prefix for /milestone' do - type_in_content_editor '/milestone' - - expect(find(suggestions_dropdown)).to have_text('/milestone') - send_keys :enter - - expect(page).to have_text('/milestone %') - end - - it 'scrolls selected item into view when navigating with keyboard' do - type_in_content_editor '/' - - expect(find(suggestions_dropdown)).to have_text('label') - - expect(dropdown_scroll_top).to be 0 - - send_keys :arrow_up - - expect(dropdown_scroll_top).to be > 100 - end - - end - - it 'shows suggestions for members with descriptions' do - type_in_content_editor '@a' - - expect(find(suggestions_dropdown)).to have_text('abc123') - expect(find(suggestions_dropdown)).to have_text('all') - expect(find(suggestions_dropdown)).to have_text('Group Members') - - type_in_content_editor 'bc' - - send_keys :enter - - expect(page).not_to have_css(suggestions_dropdown) - expect(page).to have_text('@abc123') - end - - it 'allows selecting element with tab key' do - type_in_content_editor '@abc' - - expect(find(suggestions_dropdown)).to have_text('abc123') - - send_keys :tab - - expect(page).not_to have_css(suggestions_dropdown) - expect(page).to have_text('@abc123') - end - - it 'allows dismissing the suggestion popup and typing more text' do - type_in_content_editor '@ab' - - expect(find(suggestions_dropdown)).to have_text('abc123') - - send_keys :escape - - expect(page).not_to have_css(suggestions_dropdown) - - type_in_content_editor :enter - type_in_content_editor 'foobar' - - # ensure that the texts are in separate paragraphs - expect(page).to have_selector('p', text: '@ab') - expect(page).to have_selector('p', text: 'foobar') - expect(page).not_to have_selector('p', text: '@abfoobar') - end - - it 'allows typing more text after the popup has disappeared because no suggestions match' do - type_in_content_editor '@ab' - - expect(find(suggestions_dropdown)).to have_text('abc123') - - type_in_content_editor 'foo' - type_in_content_editor :enter - type_in_content_editor 'bar' - - # ensure that the texts are in separate paragraphs - expect(page).to have_selector('p', text: '@abfoo') - expect(page).to have_selector('p', text: 'bar') - expect(page).not_to have_selector('p', text: '@abfoobar') - end - - context 'when `disable_all_mention` is enabled' do - before do - stub_feature_flags(disable_all_mention: true) - end - - it 'shows suggestions for members with descriptions' do - type_in_content_editor '@a' - - expect(find(suggestions_dropdown)).to have_text('abc123') - expect(find(suggestions_dropdown)).not_to have_text('All Group Members') - - type_in_content_editor 'bc' - - send_keys [:arrow_down, :enter] - - expect(page).not_to have_css(suggestions_dropdown) - expect(page).to have_text('@abc123') - end - end - - it 'shows suggestions for merge requests' do - type_in_content_editor '!' - - expect(find(suggestions_dropdown)).to have_text('My Cool Merge Request') - - send_keys [:arrow_down, :enter] - - expect(page).not_to have_css(suggestions_dropdown) - expect(page).to have_text('!1') - end - - it 'shows suggestions for issues' do - type_in_content_editor '#' - - expect(find(suggestions_dropdown)).to have_text('My Cool Linked Issue') - - send_keys [:arrow_down, :enter] - - expect(page).not_to have_css(suggestions_dropdown) - expect(page).to have_text('#1') - end - - it 'shows suggestions for milestones' do - type_in_content_editor '%' - - expect(find(suggestions_dropdown)).to have_text('My Cool Milestone') - - send_keys [:arrow_down, :enter] - - expect(page).not_to have_css(suggestions_dropdown) - expect(page).to have_text('%My Cool Milestone') - end - - it 'shows suggestions for emojis' do - type_in_content_editor ':smile' - - expect(find(suggestions_dropdown)).to have_text('😃 smiley') - expect(find(suggestions_dropdown)).to have_text('😸 smile_cat') - - send_keys :enter - - expect(page).not_to have_css(suggestions_dropdown) - - expect(page).to have_text('😄') - end - - it 'doesn\'t show suggestions dropdown if there are no suggestions to show' do - type_in_content_editor '%' - - expect(find(suggestions_dropdown)).to have_text('My Cool Milestone') - - type_in_content_editor 'x' - - expect(page).not_to have_css(suggestions_dropdown) - end - - def dropdown_scroll_top - evaluate_script("document.querySelector('#{suggestions_dropdown}').scrollTop") - end - end -end - -RSpec.shared_examples 'inserts diagrams.net diagram using the content editor' do - include ContentEditorHelpers - - before do - switch_to_content_editor - - click_attachment_button - end - - it 'displays correct media bubble menu with edit diagram button' do - display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'diagram.drawio.svg' - - expect_media_bubble_menu_to_be_visible - - click_edit_diagram_button - - expect_drawio_editor_is_opened - end -end diff --git a/spec/support/shared_examples/features/rich_text_editor/autocomplete_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/autocomplete_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..6eb6c1e60f112de7b1ce0668834853eedaca3eb5 --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/autocomplete_shared_examples.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - autocomplete' do |params = { + with_expanded_references: true, + with_quick_actions: true +}| + include RichTextEditorHelpers + + describe 'autocomplete suggestions' do + let(:suggestions_dropdown) { '[data-testid="content-editor-suggestions-dropdown"]' } + + before do + if defined?(project) + create(:issue, project: project, title: 'My Cool Linked Issue') + create(:merge_request, source_project: project, source_branch: 'branch-1', title: 'My Cool Merge Request') + create(:label, project: project, title: 'My Cool Label') + create(:milestone, project: project, title: 'My Cool Milestone') + + project.add_maintainer(create(:user, name: 'abc123', username: 'abc123')) + else # group wikis + project = create(:project, group: group) + + create(:issue, project: project, title: 'My Cool Linked Issue') + create(:merge_request, source_project: project, source_branch: 'branch-1', title: 'My Cool Merge Request') + create(:group_label, group: group, title: 'My Cool Label') + create(:milestone, group: group, title: 'My Cool Milestone') + + project.add_maintainer(create(:user, name: 'abc123', username: 'abc123')) + end + + switch_to_content_editor + + type_in_content_editor :enter + + stub_feature_flags(disable_all_mention: false) + end + + if params[:with_expanded_references] + describe 'when expanding an issue reference' do + it 'displays full reference name' do + new_issue = create(:issue, project: project, title: 'Brand New Issue') + + type_in_content_editor "##{new_issue.iid}+s " + + expect(page).to have_text('Brand New Issue') + end + end + + describe 'when expanding an MR reference' do + it 'displays full reference name' do + new_mr = create(:merge_request, source_project: project, source_branch: 'branch-2', title: 'Brand New MR') + + type_in_content_editor "!#{new_mr.iid}+s " + + expect(page).to have_text('Brand New') + end + end + end + + if params[:with_quick_actions] + it 'shows suggestions for quick actions' do + type_in_content_editor '/a' + + expect(find(suggestions_dropdown)).to have_text('/assign') + expect(find(suggestions_dropdown)).to have_text('/label') + end + + it 'adds the correct prefix for /assign' do + type_in_content_editor '/assign' + + expect(find(suggestions_dropdown)).to have_text('/assign') + send_keys :enter + + expect(page).to have_text('/assign @') + end + + it 'adds the correct prefix for /label' do + type_in_content_editor '/label' + + expect(find(suggestions_dropdown)).to have_text('/label') + send_keys :enter + + expect(page).to have_text('/label ~') + end + + it 'adds the correct prefix for /milestone' do + type_in_content_editor '/milestone' + + expect(find(suggestions_dropdown)).to have_text('/milestone') + send_keys :enter + + expect(page).to have_text('/milestone %') + end + + it 'scrolls selected item into view when navigating with keyboard' do + type_in_content_editor '/' + + expect(find(suggestions_dropdown)).to have_text('label') + + expect(dropdown_scroll_top).to be 0 + + send_keys :arrow_up + + expect(dropdown_scroll_top).to be > 100 + end + end + + it 'shows suggestions for members with descriptions' do + type_in_content_editor '@a' + + expect(find(suggestions_dropdown)).to have_text('abc123') + expect(find(suggestions_dropdown)).to have_text('all') + expect(find(suggestions_dropdown)).to have_text('Group Members') + + type_in_content_editor 'bc' + + send_keys :enter + + expect(page).not_to have_css(suggestions_dropdown) + expect(page).to have_text('@abc123') + end + + it 'allows selecting element with tab key' do + type_in_content_editor '@abc' + + expect(find(suggestions_dropdown)).to have_text('abc123') + + send_keys :tab + + expect(page).not_to have_css(suggestions_dropdown) + expect(page).to have_text('@abc123') + end + + it 'allows dismissing the suggestion popup and typing more text' do + type_in_content_editor '@ab' + + expect(find(suggestions_dropdown)).to have_text('abc123') + + send_keys :escape + + expect(page).not_to have_css(suggestions_dropdown) + + type_in_content_editor :enter + type_in_content_editor 'foobar' + + # ensure that the texts are in separate paragraphs + expect(page).to have_selector('p', text: '@ab') + expect(page).to have_selector('p', text: 'foobar') + expect(page).not_to have_selector('p', text: '@abfoobar') + end + + it 'allows typing more text after the popup has disappeared because no suggestions match' do + type_in_content_editor '@ab' + + expect(find(suggestions_dropdown)).to have_text('abc123') + + type_in_content_editor 'foo' + type_in_content_editor :enter + type_in_content_editor 'bar' + + # ensure that the texts are in separate paragraphs + expect(page).to have_selector('p', text: '@abfoo') + expect(page).to have_selector('p', text: 'bar') + expect(page).not_to have_selector('p', text: '@abfoobar') + end + + context 'when `disable_all_mention` is enabled' do + before do + stub_feature_flags(disable_all_mention: true) + end + + it 'shows suggestions for members with descriptions' do + type_in_content_editor '@a' + + expect(find(suggestions_dropdown)).to have_text('abc123') + expect(find(suggestions_dropdown)).not_to have_text('All Group Members') + + type_in_content_editor 'bc' + + send_keys [:arrow_down, :enter] + + expect(page).not_to have_css(suggestions_dropdown) + expect(page).to have_text('@abc123') + end + end + + it 'shows suggestions for merge requests' do + type_in_content_editor '!' + + expect(find(suggestions_dropdown)).to have_text('My Cool Merge Request') + + send_keys [:arrow_down, :enter] + + expect(page).not_to have_css(suggestions_dropdown) + expect(page).to have_text('!1') + end + + it 'shows suggestions for issues' do + type_in_content_editor '#' + + expect(find(suggestions_dropdown)).to have_text('My Cool Linked Issue') + + send_keys [:arrow_down, :enter] + + expect(page).not_to have_css(suggestions_dropdown) + expect(page).to have_text('#1') + end + + it 'shows suggestions for milestones' do + type_in_content_editor '%' + + expect(find(suggestions_dropdown)).to have_text('My Cool Milestone') + + send_keys [:arrow_down, :enter] + + expect(page).not_to have_css(suggestions_dropdown) + expect(page).to have_text('%My Cool Milestone') + end + + it 'shows suggestions for emojis' do + type_in_content_editor ':smile' + + expect(find(suggestions_dropdown)).to have_text('😃 smiley') + expect(find(suggestions_dropdown)).to have_text('😸 smile_cat') + + send_keys :enter + + expect(page).not_to have_css(suggestions_dropdown) + + expect(page).to have_text('😄') + end + + it 'doesn\'t show suggestions dropdown if there are no suggestions to show' do + type_in_content_editor '%' + + expect(find(suggestions_dropdown)).to have_text('My Cool Milestone') + + type_in_content_editor 'x' + + expect(page).not_to have_css(suggestions_dropdown) + end + + def dropdown_scroll_top + evaluate_script("document.querySelector('#{suggestions_dropdown}').scrollTop") + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/code_block_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/code_block_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..66aa6c9b5763ebee1861b184fda4cb373799c175 --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/code_block_shared_examples.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - code blocks' do + include RichTextEditorHelpers + + describe 'code block' do + before do + visit(profile_preferences_path) + + find('.syntax-theme').choose('Dark') + + wait_for_requests + + page.go_back + refresh + switch_to_content_editor + end + + it 'applies theme classes to code blocks' do + expect(page).not_to have_css('.content-editor-code-block.code.highlight.dark') + + type_in_content_editor [:enter, :enter] + type_in_content_editor '```js ' # trigger input rule + type_in_content_editor 'var a = 0' + + expect(page).to have_css('.content-editor-code-block.code.highlight.dark') + end + end + + describe 'code block bubble menu' do + before do + switch_to_content_editor + end + + it 'shows a code block bubble menu for a code block' do + type_in_content_editor [:enter, :enter] + + type_in_content_editor '```js ' # trigger input rule + type_in_content_editor 'var a = 0' + type_in_content_editor [:shift, :left] + + expect(page).to have_css('[data-testid="code-block-bubble-menu"]') + end + + it 'sets code block type to "javascript" for `js`' do + type_in_content_editor [:enter, :enter] + + type_in_content_editor '```js ' + type_in_content_editor 'var a = 0' + + expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Javascript') + end + + it 'sets code block type to "Custom (nomnoml)" for `nomnoml`' do + type_in_content_editor [:enter, :enter] + + type_in_content_editor '```nomnoml ' + type_in_content_editor 'test' + + expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Custom (nomnoml)') + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/common_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/common_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..60861f774bded2300c19a53d2ba86c1a705e8575 --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/common_shared_examples.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - common' do + include RichTextEditorHelpers + + it 'saves page content in local storage if the user navigates away' do + switch_to_content_editor + + expect(page).to have_css(content_editor_testid) + + type_in_content_editor ' Typing text in the content editor' + + wait_until_hidden_field_is_updated(/Typing text in the content editor/) + + begin + refresh + rescue Selenium::WebDriver::Error::UnexpectedAlertOpenError + end + + expect(page).to have_text('Typing text in the content editor') + end + + it 'autofocuses the rich text editor when switching to rich text' do + switch_to_content_editor + + expect(page).to have_css("#{content_editor_testid}:focus") + end + + it 'autofocuses the plain text editor when switching back to markdown' do + switch_to_content_editor + switch_to_markdown_editor + + expect(page).to have_css("textarea:focus") + end + + describe 'rendering with initial content' do + it 'renders correctly with table as initial content' do + textarea = find 'textarea' + textarea.send_keys "\n\n" + textarea.send_keys "| First Header | Second Header |\n" + textarea.send_keys "|--------------|---------------|\n" + textarea.send_keys "| Content from cell 1 | Content from cell 2 |\n\n" + textarea.send_keys "Content below table" + + switch_to_content_editor + + expect(page).not_to have_text('An error occurred') + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/copy_paste_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/copy_paste_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..c5d8f90542ac5b4ca34744a632efab53d90eba3e --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/copy_paste_shared_examples.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - copy/paste' do + include RichTextEditorHelpers + + let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') } + let(:modifier_key) { is_mac ? :command : :control } + + describe 'pasting text' do + before do + switch_to_content_editor + + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor :delete + + type_in_content_editor "Some **rich** _text_ ~~content~~ [link](https://gitlab.com)" + + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor [modifier_key, 'x'] + end + + it 'pastes text with formatting if ctrl + v is pressed' do + type_in_content_editor [modifier_key, 'v'] + + page.within content_editor_testid do + expect(page).to have_selector('strong', text: 'rich') + expect(page).to have_selector('em', text: 'text') + expect(page).to have_selector('s', text: 'content') + expect(page).to have_selector('a[href="https://gitlab.com"]', text: 'link') + end + end + + it 'does not show a loading indicator after undo paste' do + type_in_content_editor [modifier_key, 'v'] + type_in_content_editor [modifier_key, 'z'] + + page.within content_editor_testid do + expect(page).not_to have_css('.gl-dots-loader') + end + end + + it 'pastes raw text without formatting if shift + ctrl + v is pressed' do + type_in_content_editor [modifier_key, :shift, 'v'] + + page.within content_editor_testid do + expect(page).to have_text('Some rich text content link') + + expect(page).not_to have_selector('strong') + expect(page).not_to have_selector('em') + expect(page).not_to have_selector('s') + expect(page).not_to have_selector('a') + end + end + + it 'pastes raw markdown with formatting when pasting inside a markdown code block' do + type_in_content_editor '```md' + type_in_content_editor :enter + type_in_content_editor [modifier_key, 'v'] + + page.within content_editor_testid do + expect(page).to have_selector('pre', text: 'Some **rich** _text_ ~~content~~ [link](https://gitlab.com)') + end + end + + it 'pastes raw markdown without formatting when pasting inside a plaintext code block' do + type_in_content_editor '```' + type_in_content_editor :enter + type_in_content_editor [modifier_key, 'v'] + + page.within content_editor_testid do + expect(page).to have_selector('pre', text: 'Some rich text content link') + end + end + + it 'pastes raw text without formatting, stripping whitespaces, if shift + ctrl + v is pressed' do + type_in_content_editor " Some **rich**" + type_in_content_editor :enter + type_in_content_editor " _text_" + type_in_content_editor :enter + type_in_content_editor " ~~content~~" + type_in_content_editor :enter + type_in_content_editor " [link](https://gitlab.com)" + + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor [modifier_key, 'x'] + type_in_content_editor [modifier_key, :shift, 'v'] + + page.within content_editor_testid do + expect(page).to have_text('Some rich text content link') + end + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/diagrams_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/diagrams_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..666ae2482a02cbedba5575261fe94849fde976d3 --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/diagrams_shared_examples.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - diagrams' do + include RichTextEditorHelpers + + describe 'mermaid diagram' do + before do + switch_to_content_editor + + type_in_content_editor [:enter, :enter] + type_in_content_editor '```mermaid ' + type_in_content_editor ['graph TD;', :enter, ' JohnDoe12 --> HelloWorld34'] + end + + it 'renders and updates the diagram correctly in a sandboxed iframe' do + iframe = find(content_editor_testid).find('iframe') + expect(iframe['src']).to include('/-/sandbox/mermaid') + + within_frame(iframe) do + expect(find('svg .nodes').text).to include('JohnDoe12') + expect(find('svg .nodes').text).to include('HelloWorld34') + end + + expect(iframe['height'].to_i).to be > 100 + + find(content_editor_testid).send_keys [:enter, ' JaneDoe34 --> HelloWorld56'] + + within_frame(iframe) do + page.has_content?('JaneDoe34') + + expect(find('svg .nodes').text).to include('JaneDoe34') + expect(find('svg .nodes').text).to include('HelloWorld56') + end + end + + it 'toggles the diagram when preview button is clicked', + quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/397682' do + find('[data-testid="preview-diagram"]').click + + expect(find(content_editor_testid)).not_to have_selector('iframe') + + find('[data-testid="preview-diagram"]').click + + iframe = find(content_editor_testid).find('iframe') + + within_frame(iframe) do + expect(find('svg .nodes').text).to include('JohnDoe12') + expect(find('svg .nodes').text).to include('HelloWorld34') + end + end + end + + describe 'drawio diagram' do + before do + switch_to_content_editor + + click_attachment_button + end + + it 'displays correct media bubble menu with edit diagram button' do + display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'diagram.drawio.svg' + + expect_media_bubble_menu_to_be_visible + + click_edit_diagram_button + + expect_drawio_editor_is_opened + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/links_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/links_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..0a5890c114f1c4015b3085b16e039d7e8f847ff9 --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/links_shared_examples.rb @@ -0,0 +1,230 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - links' do + include RichTextEditorHelpers + + describe 'creating and editing links' do + before do + switch_to_content_editor + end + + context 'when clicking the link icon in the toolbar' do + it 'shows the link bubble menu' do + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + + expect(page).to have_css('[data-testid="link-bubble-menu"]') + end + + context 'if no text is selected' do + before do + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + end + + it 'opens an empty inline modal to create a link' do + page.within '[data-testid="link-bubble-menu"]' do + expect(page).to have_field('link-text', with: '') + expect(page).to have_field('link-href', with: '') + end + end + + context 'when the user clicks the apply button' do + it 'applies the changes to the document' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'Link to GitLab home page' + fill_in 'link-href', with: 'https://gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_css('a[href="https://gitlab.com"]') + expect(page).to have_text('Link to GitLab home page') + end + end + end + + context 'when the user clicks the cancel button' do + it 'does not apply the changes to the document' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'Link to GitLab home page' + fill_in 'link-href', with: 'https://gitlab.com' + + click_button 'Cancel' + end + + page.within content_editor_testid do + expect(page).not_to have_css('a') + end + end + end + end + + context 'if text is selected' do + before do + type_in_content_editor 'The quick brown fox jumps over the lazy dog' + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + end + + it 'prefills inline modal to create a link' do + page.within '[data-testid="link-bubble-menu"]' do + expect(page).to have_field('link-text', with: 'dog') + expect(page).to have_field('link-href', with: '') + end + end + + context 'when the user clicks the apply button' do + it 'applies the changes to the document' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'new dog' + fill_in 'link-href', with: 'https://en.wikipedia.org/wiki/Shiba_Inu' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://en.wikipedia.org/wiki/Shiba_Inu"]', + text: 'new dog' + ) + end + end + end + end + end + + context 'if cursor is placed on an existing link' do + before do + type_in_content_editor 'Link to [GitLab home **page**](https://gitlab.com)' + type_in_content_editor :left + end + + it 'prefills inline modal to edit the link' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click + + expect(page).to have_field('link-text', with: 'GitLab home page') + expect(page).to have_field('link-href', with: 'https://gitlab.com') + end + end + + it 'updates the link attributes if text is not updated' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click + + fill_in 'link-href', with: 'https://about.gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]') + expect(page.find('a')).to have_text('GitLab home page') + expect(page).to have_selector('strong', text: 'page') + end + end + + it 'updates the link attributes and text if text is updated' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click + + fill_in 'link-text', with: 'GitLab about page' + fill_in 'link-href', with: 'https://about.gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]', + text: 'GitLab about page' + ) + expect(page).not_to have_selector('strong') + end + end + + it 'does nothing if Cancel is clicked' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="edit-link"]').click + + click_button 'Cancel' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://gitlab.com"]', + text: 'GitLab home page' + ) + expect(page).to have_selector('strong') + end + end + + context 'when the user clicks the unlink button' do + it 'removes the link' do + page.within '[data-testid="link-bubble-menu"]' do + page.find('[data-testid="remove-link"]').click + end + + page.within content_editor_testid do + expect(page).not_to have_selector('a') + expect(page).to have_selector('strong', text: 'page') + end + end + end + end + + context 'when selection spans more than a link' do + before do + type_in_content_editor 'a [b **c**](https://gitlab.com)' + + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + type_in_content_editor [:shift, :left] + + page.find('[data-testid="formatting-toolbar"] [data-testid="link"]').click + end + + it 'prefills inline modal with the entire selection' do + page.within '[data-testid="link-bubble-menu"]' do + expect(page).to have_field('link-text', with: 'a b c') + expect(page).to have_field('link-href', with: '') + end + end + + it 'expands the link and updates the link attributes if text is not updated' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-href', with: 'https://about.gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]') + expect(page.find('a')).to have_text('a b c') + expect(page).to have_selector('strong', text: 'c') + end + end + + it 'expands the link, updates the link attributes and text if text is updated', + quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/419684' do + page.within '[data-testid="link-bubble-menu"]' do + fill_in 'link-text', with: 'new text' + fill_in 'link-href', with: 'https://about.gitlab.com' + + click_button 'Apply' + end + + page.within content_editor_testid do + expect(page).to have_selector('a[href="https://about.gitlab.com"]', + text: 'new text' + ) + expect(page).not_to have_selector('strong') + end + end + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..9e5976d126f94a3e5d1d5aa6a6f5eb74fbdb48ef --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/media_shared_examples.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - media' do + include RichTextEditorHelpers + + describe 'media elements bubble menu' do + before do + switch_to_content_editor + + click_attachment_button + end + + it 'displays correct media bubble menu for images', :js do + display_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'dk.png' + + expect_media_bubble_menu_to_be_visible + end + + it 'displays correct media bubble menu for video', :js do + display_media_bubble_menu '[data-testid="content_editor_editablebox"] video', 'video_sample.mp4' + + expect_media_bubble_menu_to_be_visible + end + end +end diff --git a/spec/support/shared_examples/features/rich_text_editor/selection_shared_examples.rb b/spec/support/shared_examples/features/rich_text_editor/selection_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..fc94239b9be08867f75498f33b4eae9eec527f1b --- /dev/null +++ b/spec/support/shared_examples/features/rich_text_editor/selection_shared_examples.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'rich text editor - selection' do + include RichTextEditorHelpers + + let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') } + let(:modifier_key) { is_mac ? :command : :control } + + describe 'selecting text' do + before do + switch_to_content_editor + + # delete all text first + type_in_content_editor [modifier_key, 'a'] + type_in_content_editor :backspace + + type_in_content_editor 'The quick **brown** fox _jumps_ over the lazy dog!' + type_in_content_editor :enter + type_in_content_editor '[Link](https://gitlab.com)' + type_in_content_editor :enter + type_in_content_editor 'Jackdaws love my ~~big~~ sphinx of quartz!' + + # select all text + type_in_content_editor [modifier_key, 'a'] + end + + it 'renders selected text in a .content-editor-selection class' do + page.within content_editor_testid do + assert_selected 'The quick' + assert_selected 'brown' + assert_selected 'fox' + assert_selected 'jumps' + assert_selected 'over the lazy dog!' + + assert_selected 'Link' + + assert_selected 'Jackdaws love my' + assert_selected 'big' + assert_selected 'sphinx of quartz!' + end + end + + def assert_selected(text) + expect(page).to have_selector('.content-editor-selection', text: text) + end + end +end diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb index 09444084eb9ac696b5f08ec062e07adb09f241ee..fb1e3ad2f69cdedebc3c2d257447ed07ba448aa5 100644 --- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb @@ -149,12 +149,12 @@ end end - it_behaves_like 'edits content using the content editor', { + it_behaves_like 'rich text editor - common' + it_behaves_like 'rich text editor - autocomplete', { with_expanded_references: false, with_quick_actions: false } - it_behaves_like 'inserts diagrams.net diagram using the content editor' - it_behaves_like 'autocompletes items' + it_behaves_like 'rich text editor - diagrams' end context 'when the page is in a subdir', :js do