discourse/spec/system/composer/drafts_spec.rb
Martin Brennan bef35e2cd4
FIX: Handle cancel action in topic reply choice dialog (#35534)
Steps to reproduce:

* Start drafting a reply to a topic
* Write some text.
* Navigate to a different topic while keeping the draft open
* Click reply on the composer and choose cancel in the modal asking
which topic you want to reply to.
* Write some more text.
* Copy the text you have written or memorize your input.
* Reload the page/ close the composer.
* Navigate back to the topic you replied to
* Open your draft.
* Everything written after clicking cancel is missing.

This was happening because we weren't doing anything on Cancel for
the topic reply choice dialog, and we were doing `disableDrafts` before
opening the dialog, so drafts never resumed.

The fix is to just save the current draft on cancel then turn on
draft saving again so the user can continue typing and delay the
question about "Which topic do you want to reply to?" until later.

c.f.
https://meta.discourse.org/t/draft-is-no-longer-automatically-saved-after-you-cancel-replying/386370

Also rename TopicLabelContent to TopicReplyChoiceDialog, it
is more specific and reflects what the component actually does.
2025-10-22 14:42:57 +10:00

308 lines
9.7 KiB
Ruby
Vendored
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# frozen_string_literal: true
describe "Composer - Drafts", type: :system do
fab!(:topic, :topic_with_op)
fab!(:current_user, :admin)
let(:toasts) { PageObjects::Components::Toasts.new }
let(:topic_page) { PageObjects::Pages::Topic.new }
let(:composer) { PageObjects::Components::Composer.new }
let(:discard_draft_modal) { PageObjects::Modals::DiscardDraft.new }
before { sign_in(current_user) }
context "when clicking X (save and close)" do
it "saves the draft and shows a toast" do
visit "/new-topic"
expect(composer).to be_opened
composer.fill_title("this is a test topic")
composer.fill_content("a b c d e f g")
composer.close
expect(toasts).to have_success(I18n.t("js.composer.draft_saved"))
expect(Draft.where(user: current_user).count).to eq(1)
end
context "when only a title and category is specified" do
fab!(:category_1, :category)
fab!(:category_2, :category)
it "saves the draft and shows a toast" do
visit "/new-topic"
expect(composer).to be_opened
composer.fill_title("this is a test topic")
composer.switch_category(category_1.name)
composer.close
expect(toasts).to have_success(I18n.t("js.composer.draft_saved"))
expect(Draft.where(user: current_user).count).to eq(1)
end
end
context "when only title is specified and it is too short" do
it "does not save the draft or show a toast" do
visit "/new-topic"
expect(composer).to be_opened
composer.fill_title("test")
composer.close
expect(composer).to be_closed
expect(toasts).to have_no_message
expect(Draft.where(user: current_user).count).to eq(0)
end
end
end
context "when clicking discard" do
let(:dialog) { PageObjects::Components::Dialog.new }
before { Jobs.run_immediately! }
it "does not show confirmation if there is no user input in the composer" do
visit "/new-topic"
expect(composer).to be_opened
composer.discard
expect(discard_draft_modal).to be_closed
expect(composer).to be_closed
end
it "destroys draft after discard confirmation" do
visit "/new-topic"
composer.fill_title("this is a test topic")
composer.fill_content("a b c d e f g")
try_until_success(reason: "Relies on an Ember debounce to update the draft") do
expect(Draft.where(user: current_user).count).to eq(1)
end
composer.discard
expect(discard_draft_modal).to be_open
discard_draft_modal.click_discard
expect(discard_draft_modal).to be_closed
expect(composer).to be_closed
expect(Draft.where(user: current_user).count).to eq(0)
end
context "when only a title and category is specified" do
fab!(:category_1, :category)
fab!(:category_2, :category)
it "shows Discard draft confirmation modal and hides it on Cancel button click" do
visit "/new-topic"
expect(composer).to be_opened
composer.fill_title("this is a test topic")
composer.switch_category(category_1.name)
composer.discard
expect(discard_draft_modal).to be_open
discard_draft_modal.click_cancel
expect(discard_draft_modal).to be_closed
end
end
end
context "when editing different post" do
fab!(:post_1) { Fabricate(:post, topic:, user: current_user) }
fab!(:post_2) { Fabricate(:post, topic:, user: current_user) }
it "shows the discard modal when there are changes in the composer" do
topic_page.visit_topic(post_1.topic)
topic_page.click_post_action_button(post_1, :edit)
composer.fill_content("a b c d e f g")
composer.minimize
topic_page.click_post_action_button(post_2, :edit)
expect(discard_draft_modal).to be_open
end
it "doesn't show the discard modal when there are no changes in the composer" do
topic_page.visit_topic(post_1.topic)
topic_page.click_post_action_button(post_1, :edit)
composer.minimize
topic_page.click_post_action_button(post_2, :edit)
expect(discard_draft_modal).to be_closed
expect(composer).to be_opened
end
end
context "when editing the same post" do
fab!(:post_1) { Fabricate(:post, topic:, user: current_user) }
it "doesnt show the discard modal even if there are changes in the composer" do
topic_page.visit_topic(post_1.topic)
topic_page.click_post_action_button(post_1, :edit)
composer.fill_content("a b c d e f g")
composer.minimize
topic_page.click_post_action_button(post_1, :edit)
expect(discard_draft_modal).to be_closed
expect(composer).to be_opened
expect(composer).to have_content("a b c d e f g")
composer.minimize
expect(composer).to be_minimized
topic_page.click_post_action_button(post_1, :edit)
expect(discard_draft_modal).to be_closed
expect(composer).to be_opened
end
it "doesnt show the discard modal when there are no changes in the composer" do
topic_page.visit_topic(post_1.topic)
topic_page.click_post_action_button(post_1, :edit)
composer.minimize
topic_page.click_post_action_button(post_1, :edit)
expect(discard_draft_modal).to be_closed
expect(composer).to be_opened
end
end
context "when replying to a different topic with an active draft" do
fab!(:other_topic, :topic_with_op)
let(:topic_reply_choice_dialog) { PageObjects::Components::TopicReplyChoiceDialog.new }
let(:topic_list) { PageObjects::Components::TopicList.new }
before do
topic.first_post.update!(raw: "This is the original topic OP content.")
topic.first_post.rebake!
other_topic.first_post.update!(raw: "This is the other topic OP content.")
other_topic.first_post.rebake!
end
def visit_topic_and_save_draft
topic_page.visit_topic(topic)
topic_page.click_reply_button
expect(composer).to be_opened
composer.fill_content("a b c d e f g")
composer.close
expect(toasts).to have_success(I18n.t("js.composer.draft_saved"))
expect(Draft.where(user: current_user).count).to eq(1)
topic_page.visit_topic(topic)
topic_page.click_reply_button
expect(composer).to be_opened
expect(composer).to have_content("a b c d e f g")
end
context "when clicking the original topic in the topic reply choice dialog" do
it "replies with the current content to the original topic" do
visit_topic_and_save_draft
# We have to navigate by clicking through the app to keep the
# composer open.
click_logo
expect(topic_list).to have_topic(other_topic)
topic_list.visit_topic(other_topic)
expect(topic_page).to have_post_content(
post_number: 1,
content: "This is the other topic OP content.",
)
composer.create
expect(topic_reply_choice_dialog).to be_open
expect(topic_reply_choice_dialog).to have_reply_on_original_topic(topic)
topic_reply_choice_dialog.click_reply_on_original
expect(topic_reply_choice_dialog).to be_closed
expect(composer).to be_closed
expect(topic_page).to have_post_content(
post_number: 1,
content: "This is the original topic OP content.",
)
expect(topic_page).to have_post_content(post_number: 2, content: "a b c d e f g")
expect(topic.reload.posts.last.raw).to eq("a b c d e f g")
end
end
context "when clicking the new topic in the topic reply choice dialog" do
it "replies with the current content to the new topic" do
visit_topic_and_save_draft
# We have to navigate by clicking through the app to keep the
# composer open.
click_logo
expect(topic_list).to have_topic(other_topic)
topic_list.visit_topic(other_topic)
expect(topic_page).to have_post_content(
post_number: 1,
content: "This is the other topic OP content.",
)
composer.create
expect(topic_reply_choice_dialog).to be_open
expect(topic_reply_choice_dialog).to have_reply_here_topic(other_topic)
topic_reply_choice_dialog.click_reply_here
expect(topic_reply_choice_dialog).to be_closed
expect(composer).to be_closed
expect(topic_page).to have_post_content(
post_number: 1,
content: "This is the other topic OP content.",
)
expect(topic_page).to have_post_content(post_number: 2, content: "a b c d e f g")
expect(other_topic.reload.posts.last.raw).to eq("a b c d e f g")
end
end
context "when clicking Cancel in the topic reply choice dialog" do
it "saves the current draft and will save future changes to the draft" do
visit_topic_and_save_draft
# We have to navigate by clicking through the app to keep the
# composer open.
click_logo
expect(topic_list).to have_topic(other_topic)
topic_list.visit_topic(other_topic)
expect(topic_page).to have_post_content(
post_number: 1,
content: "This is the other topic OP content.",
)
composer.create
expect(topic_reply_choice_dialog).to be_open
topic_reply_choice_dialog.click_cancel
expect(topic_reply_choice_dialog).to be_closed
composer.fill_content("This is my updated draft content, wow very impressive.")
try_until_success(reason: "Relies on waiting a few seconds for the draft to autosave") do
draft = Draft.where(user: current_user).first
expect(JSON.parse(draft.data)["reply"]).to eq(
"This is my updated draft content, wow very impressive.",
)
end
end
end
end
end