mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-27 08:42:09 +08:00
Creates a notification for watching/tracking users when an answer to a solved topic is selected. The notification: - shows the user who marked the solution as accepted - has a link to the topic - does not notify the accepting user In the topic tracking dropdown we also update the copy for solved topics to reflect the solved notification. ### How it looks Updated description in the tracking dropdown: <img width="500" alt="Screenshot 2026-03-25 at 5 39 09 PM" src="https://github.com/user-attachments/assets/641baf3c-5fe1-4946-b4b4-59dabcf22973" /> How the notification looks to a watching user: <img width="300" alt="Screenshot 2026-03-25 at 5 37 00 PM" src="https://github.com/user-attachments/assets/1d475e93-ba64-4979-84e5-4b0bb1245d42" /> Internal ref - /t/173086
193 lines
5.6 KiB
Ruby
Vendored
193 lines
5.6 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe DiscourseSolved::UnacceptAnswer do
|
|
describe ::DiscourseSolved::UnacceptAnswer::Contract, type: :model do
|
|
it { is_expected.to validate_presence_of(:post_id) }
|
|
end
|
|
|
|
describe ".call" do
|
|
subject(:result) { described_class.call(params:, **dependencies) }
|
|
|
|
fab!(:user)
|
|
fab!(:topic) { Fabricate(:topic, user:) }
|
|
fab!(:post_1, :post) { Fabricate(:post, topic:) }
|
|
fab!(:post) { Fabricate(:post, topic:) }
|
|
|
|
let(:params) { { post_id: post.id } }
|
|
let(:guardian) { user.guardian }
|
|
let(:dependencies) { { guardian: } }
|
|
|
|
before do
|
|
SiteSetting.solved_enabled = true
|
|
SiteSetting.allow_solved_on_all_topics = true
|
|
end
|
|
|
|
context "when contract is invalid" do
|
|
let(:params) { {} }
|
|
|
|
it { is_expected.to fail_a_contract }
|
|
end
|
|
|
|
context "when post is not found" do
|
|
let(:params) { { post_id: -1 } }
|
|
|
|
it { is_expected.to fail_to_find_a_model(:post) }
|
|
end
|
|
|
|
context "when post is trashed" do
|
|
before { post.trash! }
|
|
|
|
it { is_expected.to fail_to_find_a_model(:post) }
|
|
|
|
context "when user is staff" do
|
|
fab!(:user, :admin)
|
|
|
|
it { is_expected.to run_successfully }
|
|
end
|
|
end
|
|
|
|
context "when topic is not found" do
|
|
before { post.topic.destroy! }
|
|
|
|
it { is_expected.to fail_to_find_a_model(:topic) }
|
|
end
|
|
|
|
context "when topic is trashed" do
|
|
before { post.topic.trash! }
|
|
|
|
it { is_expected.to fail_to_find_a_model(:topic) }
|
|
|
|
context "when user is staff" do
|
|
fab!(:user, :admin)
|
|
|
|
it { is_expected.to run_successfully }
|
|
end
|
|
end
|
|
|
|
context "when user cannot unaccept answer" do
|
|
let(:guardian) { Guardian.new }
|
|
|
|
it { is_expected.to fail_a_policy(:can_unaccept_answer) }
|
|
end
|
|
|
|
context "when the post is not the accepted answer" do
|
|
it { is_expected.to run_successfully }
|
|
|
|
it "does not mark the topic as unsolved" do
|
|
expect { result }.not_to change { DiscourseSolved::SolvedTopic.count }
|
|
end
|
|
end
|
|
|
|
context "when the post is the accepted answer" do
|
|
fab!(:solved_topic) { Fabricate(:solved_topic, topic:, answer_post: post, accepter: user) }
|
|
|
|
let(:messages) { MessageBus.track_publish("/topic/#{topic.id}") { result } }
|
|
let(:events) { DiscourseEvent.track_events(:unaccepted_solution) { result } }
|
|
|
|
before do
|
|
UserAction.log_action!(
|
|
action_type: UserAction::SOLVED,
|
|
user_id: post.user_id,
|
|
acting_user_id: user.id,
|
|
target_post_id: post.id,
|
|
target_topic_id: topic.id,
|
|
)
|
|
Notification.create!(
|
|
notification_type: Notification.types[:custom],
|
|
user_id: post.user_id,
|
|
topic_id: post.topic_id,
|
|
post_number: post.post_number,
|
|
data: { message: "solved.accepted_notification" }.to_json,
|
|
)
|
|
end
|
|
|
|
it { is_expected.to run_successfully }
|
|
|
|
it "revokes the post author's solved credit" do
|
|
expect { result }.to change {
|
|
UserAction.where(action_type: UserAction::SOLVED, target_post: post).count
|
|
}.by(-1)
|
|
end
|
|
|
|
it "removes the accepted answer notification" do
|
|
expect { result }.to change {
|
|
Notification.where(
|
|
notification_type: Notification.types[:custom],
|
|
user: post.user,
|
|
topic: post.topic,
|
|
post_number: post.post_number,
|
|
).count
|
|
}.by(-1)
|
|
end
|
|
|
|
it "marks the topic as unsolved" do
|
|
expect { result }.to change { DiscourseSolved::SolvedTopic.count }.by(-1)
|
|
end
|
|
|
|
context "when tracking/watching users have topic solved notifications" do
|
|
fab!(:watching_user, :user)
|
|
|
|
before do
|
|
Notification.create!(
|
|
notification_type: Notification.types[:custom],
|
|
user_id: watching_user.id,
|
|
topic_id: post.topic_id,
|
|
post_number: post.post_number,
|
|
data: { message: "solved.topic_solved_notification" }.to_json,
|
|
)
|
|
end
|
|
|
|
it "removes topic solved notifications" do
|
|
expect { result }.to change {
|
|
Notification.where(
|
|
notification_type: Notification.types[:custom],
|
|
user: watching_user,
|
|
topic: post.topic,
|
|
).count
|
|
}.by(-1)
|
|
end
|
|
end
|
|
|
|
context "when an unaccepted_solution webhook is active" do
|
|
fab!(:web_hook) { Fabricate(:web_hook, active: true) }
|
|
fab!(:unaccepted_solution_event_type) do
|
|
WebHookEventType.find_by(name: "unaccepted_solution")
|
|
end
|
|
|
|
before { web_hook.web_hook_event_types << unaccepted_solution_event_type }
|
|
|
|
it "enqueues the webhook" do
|
|
expect { result }.to change { Jobs::EmitWebHookEvent.jobs.size }.by(1)
|
|
end
|
|
end
|
|
|
|
it "triggers the :unaccepted_solution event" do
|
|
expect(events).to include(a_hash_including(params: [post]))
|
|
end
|
|
|
|
it "broadcasts the unaccepted solution" do
|
|
expect(messages).to include(
|
|
an_object_having_attributes(data: a_hash_including(type: :unaccepted_solution)),
|
|
)
|
|
end
|
|
|
|
context "when the post is trashed" do
|
|
fab!(:user, :admin)
|
|
|
|
before { post.trash! }
|
|
|
|
it "still marks the topic as unsolved" do
|
|
expect { result }.to change { DiscourseSolved::SolvedTopic.count }.by(-1)
|
|
end
|
|
end
|
|
|
|
context "when a different post is the accepted answer" do
|
|
before { solved_topic.update!(answer_post: post_1) }
|
|
|
|
it "does not mark the topic as unsolved" do
|
|
expect { result }.not_to change { DiscourseSolved::SolvedTopic.count }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|