mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-25 20:15:50 +08:00
When editing a post, the composer's cancel button said "Discard" and the confirmation modal said "Do you want to discard your post?" — which sounds like you're deleting the post itself. Additionally, the modal appeared even when no edits were made. **BEFORE** | Scenario | Cancel button | Modal message | Modal OK | |-----------------|---------------|------------------------------------|----------| | Create topic | Discard | Do you want to discard your post? | Discard | | Reply | Discard | Do you want to discard your post? | Discard | | Edit | Discard | Do you want to discard your post? | Discard | | Private message | Discard | Do you want to discard your post? | Discard | \+ 🐛 bug: edit modal appears even with no changes **AFTER** | Scenario | Cancel button | Modal message | Modal OK | |-----------------|---------------|---------------------------------------|-----------------| | Create topic | Discard | Do you want to discard your post? | Discard | | Reply | Discard | Do you want to discard your post? | Discard | | Edit | Cancel edit | Do you want to discard your changes? | Discard changes | | Private message | Discard | Do you want to discard your post? | Discard | The false dirty state had two causes: 1. `anyDirty` included `titleDirty` even when editing non-first posts where the title isn't editable. The title was set to the topic title during the `serialize(_edit_topic_serializer)` call but `originalTitle` was only set for first posts, making `titleDirty` always true. Fixed by scoping `titleDirty` in `anyDirty` to only apply when `canEditTitle` is true, and by always setting `originalTitle` when loading a post for editing. 2. `hasMetaData` had inverted logic — `isEmpty(Object.keys(this.metaData))` returned true when metadata was empty, not when it had keys. https://meta.discourse.org/t/399942
452 lines
14 KiB
Ruby
Vendored
452 lines
14 KiB
Ruby
Vendored
# frozen_string_literal: true
|
||
|
||
describe "Composer - Drafts" 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 less than min_topic_title_length" do
|
||
it "does saves the draft and shows a toast" do
|
||
visit "/new-topic"
|
||
|
||
expect(composer).to be_opened
|
||
composer.fill_title("x")
|
||
composer.close
|
||
expect(composer).to be_closed
|
||
|
||
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 body is specified and it is less than min_post_length" do
|
||
it "does saves the draft and shows a toast" do
|
||
visit "/new-topic"
|
||
|
||
expect(composer).to be_opened
|
||
composer.fill_content("x")
|
||
composer.close
|
||
expect(composer).to be_closed
|
||
|
||
expect(toasts).to have_success(I18n.t("js.composer.draft_saved"))
|
||
expect(Draft.where(user: current_user).count).to eq(1)
|
||
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 discarding while editing a post" do
|
||
fab!(:post_1) { Fabricate(:post, topic:, user: current_user) }
|
||
|
||
before { Jobs.run_immediately! }
|
||
|
||
it "does not show the discard modal when there are no changes" do
|
||
topic_page.visit_topic(post_1.topic)
|
||
topic_page.click_post_action_button(post_1, :edit)
|
||
|
||
expect(composer).to be_opened
|
||
composer.discard
|
||
|
||
expect(discard_draft_modal).to be_closed
|
||
expect(composer).to be_closed
|
||
end
|
||
|
||
it "does not show the discard modal when editing the first post with no changes" do
|
||
first_post = topic.first_post
|
||
first_post.update!(user: current_user)
|
||
|
||
topic_page.visit_topic(topic)
|
||
topic_page.click_post_action_button(first_post, :edit)
|
||
|
||
expect(composer).to be_opened
|
||
composer.discard
|
||
|
||
expect(discard_draft_modal).to be_closed
|
||
expect(composer).to be_closed
|
||
end
|
||
|
||
it "shows the discard modal with editing-specific text when there are changes" do
|
||
topic_page.visit_topic(post_1.topic)
|
||
topic_page.click_post_action_button(post_1, :edit)
|
||
composer.fill_content("edited content here")
|
||
composer.discard
|
||
|
||
expect(discard_draft_modal).to be_open
|
||
expect(page).to have_css(
|
||
".discard-draft-modal",
|
||
text: I18n.t("js.post.cancel_composer.confirm_edit"),
|
||
)
|
||
expect(page).to have_css(
|
||
".discard-draft-modal__discard-btn",
|
||
text: I18n.t("js.post.cancel_composer.discard_edit"),
|
||
)
|
||
end
|
||
|
||
it "shows the discard modal with editing-specific text when editing the first post" do
|
||
first_post = topic.first_post
|
||
first_post.update!(user: current_user)
|
||
|
||
topic_page.visit_topic(topic)
|
||
topic_page.click_post_action_button(first_post, :edit)
|
||
composer.fill_content("edited content here")
|
||
composer.discard
|
||
|
||
expect(discard_draft_modal).to be_open
|
||
expect(page).to have_css(
|
||
".discard-draft-modal",
|
||
text: I18n.t("js.post.cancel_composer.confirm_edit"),
|
||
)
|
||
end
|
||
|
||
it "shows the cancel edit button instead of discard" do
|
||
topic_page.visit_topic(post_1.topic)
|
||
topic_page.click_post_action_button(post_1, :edit)
|
||
|
||
expect(composer).to be_opened
|
||
expect(page).to have_css(".discard-button", text: I18n.t("js.composer.cancel_edit"))
|
||
end
|
||
end
|
||
|
||
context "when discarding while creating a post" do
|
||
before { Jobs.run_immediately! }
|
||
|
||
it "shows the discard modal with post-specific text when there are changes" do
|
||
visit "/new-topic"
|
||
|
||
composer.fill_title("this is a test topic")
|
||
composer.fill_content("a b c d e f g")
|
||
composer.discard
|
||
|
||
expect(discard_draft_modal).to be_open
|
||
expect(page).to have_css(
|
||
".discard-draft-modal",
|
||
text: I18n.t("js.post.cancel_composer.confirm"),
|
||
)
|
||
expect(page).to have_css(
|
||
".discard-draft-modal__discard-btn",
|
||
text: I18n.t("js.post.cancel_composer.discard"),
|
||
)
|
||
end
|
||
|
||
it "shows the discard button" do
|
||
visit "/new-topic"
|
||
|
||
expect(composer).to be_opened
|
||
expect(page).to have_css(".discard-button", text: I18n.t("js.composer.discard"))
|
||
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 "doesn’t 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 "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_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
|
||
|
||
context "when replying as a linked topic" do
|
||
fab!(:category)
|
||
|
||
it "does not leave a stale reply draft after posting a linked topic" do
|
||
topic_page.visit_topic(topic)
|
||
topic_page.click_reply_button
|
||
|
||
expect(composer).to be_opened
|
||
composer.fill_content("this is a reply draft")
|
||
|
||
try_until_success(reason: "wait for draft to be saved") do
|
||
expect(Draft.where(user: current_user).count).to eq(1)
|
||
end
|
||
|
||
composer.open_composer_actions
|
||
composer.select_action(I18n.t("js.composer.composer_actions.reply_as_new_topic.label"))
|
||
|
||
expect(composer).to be_opened
|
||
composer.fill_title("This is a linked topic title for testing")
|
||
composer.fill_content("this is a linked topic body content for testing")
|
||
composer.switch_category(category.name)
|
||
composer.create
|
||
|
||
expect(composer).to be_closed
|
||
expect(Topic.last.title).to eq("This is a linked topic title for testing")
|
||
expect(Draft.where(user: current_user).count).to eq(0)
|
||
end
|
||
end
|
||
end
|