0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-11 02:59:07 +08:00
discourse/plugins/discourse-topic-voting/spec/system/voting_spec.rb
Régis Hanol a9d2e8272a
FIX: Show the voting notification prompt when vote limits are disabled (#41731)
When topic voting vote limits are disabled, the "Notify me about new
posts"
prompt shown after voting never appeared. That prompt is part of the
menu the
vote button opens, but the menu was only rendered when vote limits were
enabled — with limits off the button had no menu at all, so both the
notification prompt and the remove-vote action were unreachable.

The fix renders the menu for any signed-in user rather than gating it on
vote
limits. The remaining-votes rows stay tied to limits being enabled,
while
remove-vote and watch-topic show whenever the user has voted. Removing a
vote
with limits disabled now goes through the menu, matching the behavior
when
limits are enabled.

Reported at
https://meta.discourse.org/t/disabling-vote-limits-seems-to-break-notification-prompt-in-topic-voting/407512

The second commit is follow-up housekeeping on the plugin, kept separate
from
the fix: removing dead code and duplication in the vote components and
initializers, counting a user's votes with `COUNT` instead of loading
every
row on each current-user serialization, and deleting orphaned i18n keys
and a
route-map filename left over from the plugin's former name.

### Testing

- Added a system spec covering the notification prompt appearing after
voting
  with vote limits disabled.
- Existing topic-voting system and JS specs pass; the model spec still
covers
  the trust-level-0 lock via `reached_voting_limit?`.
2026-07-15 14:54:37 +02:00

299 lines
11 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe "Topic voting" do
fab!(:user)
fab!(:admin) { Fabricate(:admin, trust_level: TrustLevel[4]) }
fab!(:category1, :category)
fab!(:category2, :category)
fab!(:voting_category) { Fabricate(:category, name: "voting category") }
fab!(:topic1) { Fabricate(:topic, category: category1) }
fab!(:topic2) { Fabricate(:topic, category: category1) }
fab!(:topic3) { Fabricate(:topic, category: category2) }
fab!(:voting_topic1) { Fabricate(:topic, category: voting_category) }
fab!(:voting_topic2) { Fabricate(:topic, category: voting_category) }
fab!(:voting_topic3) { Fabricate(:topic, category: voting_category) }
fab!(:voting_topic4) { Fabricate(:topic, category: voting_category) }
fab!(:post1) { Fabricate(:post, topic: topic1) }
fab!(:post2) { Fabricate(:post, topic: topic2) }
let(:category_page) { PageObjects::Pages::Category.new }
let(:banner) { PageObjects::Components::AdminChangesBanner.new }
let(:topic_page) { PageObjects::Pages::Topic.new }
let(:user_page) { PageObjects::Pages::User.new }
let(:admin_page) { PageObjects::Pages::AdminSiteSettings.new }
let(:form) { PageObjects::Components::FormKit.new(".form-kit") }
let(:toast) { PageObjects::Components::Toasts.new }
before do
SiteSetting.topic_voting_enabled = true
sign_in(admin)
end
it "enables voting in category topics and votes" do
category_page.visit(category1)
expect(category_page).to have_no_css(category_page.votes)
category_page.visit_general(category1)
category_type_selector = PageObjects::Components::DMenu.new(".category-type-selector")
category_type_selector.expand
category_type_selector.option(".category-type-selector__result.--category-type-ideas").click
banner.click_save
try_until_success { expect(Category.can_vote?(category1.id)).to eq(true) }
# make a vote
category_page.visit(category1)
expect(category_page).to have_css(category_page.votes)
category_page.select_topic(topic1)
expect(topic_page.vote_count).to have_text("0")
topic_page.vote
expect(topic_page.vote_popup).to have_text(
I18n.t("js.topic_voting.see_votes", count: 9, max: 10),
)
expect(topic_page.vote_count).to have_text("1")
# visit user activity page
topic_page.click_my_votes
expect(user_page.active_user_primary_navigation).to have_text("Activity")
expect(user_page.active_user_secondary_navigation).to have_text("Votes")
expect(page).to have_css(".topic-list-body tr[data-topic-id=\"#{topic1.id}\"]")
find(".topic-list-body tr[data-topic-id=\"#{topic1.id}\"] a.raw-link").click
# unvoting
topic_page.remove_vote
expect(topic_page.vote_count).to have_text("0")
end
context "when navigating between voting topics" do
fab!(:voting_post1) { Fabricate(:post, topic: voting_topic1) }
fab!(:voting_post2) { Fabricate(:post, topic: voting_topic2) }
fab!(:voting_post3) { Fabricate(:post, topic: voting_topic3) }
before { DiscourseTopicVoting::CategorySetting.create!(category: voting_category) }
it "resets vote UI state after route transitions" do
voting_topic3.update!(closed: true)
Fabricate(:post, topic: voting_topic3, raw: "Check out #{voting_topic1.url}")
Fabricate(:post, topic: voting_topic1, raw: "Check out #{voting_topic2.url}")
visit("/t/#{voting_topic3.slug}/#{voting_topic3.id}")
expect(page).to have_css("button.voting-wrapper__button[disabled]")
find("a[href='#{voting_topic1.url}']").click
expect(page).to have_no_css("button.voting-wrapper__button[disabled]")
topic_page.vote
expect(topic_page.vote_popup).to have_text(
I18n.t("js.topic_voting.see_votes", count: 9, max: 10),
)
find("a[href='#{voting_topic2.url}']").click
expect(page).to have_no_css("button.voting-wrapper__button[disabled]")
topic_page.vote
expect(topic_page.vote_popup).to have_text(
I18n.t("js.topic_voting.see_votes", count: 8, max: 10),
)
end
end
context "when toggling watch from the vote menu" do
fab!(:voting_post) { Fabricate(:post, topic: voting_topic1) }
before { DiscourseTopicVoting::CategorySetting.create!(category: voting_category) }
it "sets the topic to watching" do
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
topic_page.vote
expect(topic_page).to have_watch_toggle_off
topic_page.click_watch_toggle
expect(topic_page).to have_watch_toggle_on
expect(TopicUser.find_by(user: admin, topic: voting_topic1).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
end
end
context "when viewing a closed voting topic without having voted" do
before { DiscourseTopicVoting::CategorySetting.create!(category: voting_category) }
it "does not show the remove vote button" do
voting_topic1.update!(closed: true)
Fabricate(:post, topic: voting_topic1)
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
expect(page).to have_css("button.voting-wrapper__button[disabled]")
expect(topic_page).to have_no_remove_vote_button
end
end
context "when no votes are left" do
before do
DiscourseTopicVoting::CategorySetting.create!(category: category1)
SiteSetting.topic_voting_tl4_vote_limit = 1
end
it "alerts the user" do
category_page.visit(category1).select_topic(topic1)
topic_page.vote
expect(topic_page.vote_popup).to have_text(
I18n.t("js.topic_voting.see_votes", count: 0, max: 1),
)
end
end
context "when viewing as anonymous user" do
fab!(:voting_post) { Fabricate(:post, topic: voting_topic1) }
before do
DiscourseTopicVoting::CategorySetting.create!(category: voting_category)
Capybara.reset_session!
end
it "redirects to login when clicking vote" do
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
find(".title-voting button.voting-wrapper__button").click
expect(page).to have_current_path("/login")
end
end
context "when scrolling down on a voting topic" do
fab!(:voting_posts) { Fabricate.times(21, :post, topic: voting_topic1) }
before { DiscourseTopicVoting::CategorySetting.create!(category: voting_category) }
it "shows voting in the docked header" do
sign_in(admin)
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
expect(page).to have_css(".title-voting")
expect(page).to have_no_css(".header-title-voting")
page.execute_script("document.querySelector('#post_4').scrollIntoView()")
expect(page).to have_css(".header-title-voting .voting-wrapper")
end
end
context "when viewing who voted" do
fab!(:voting_post) { Fabricate(:post, topic: voting_topic1) }
before do
DiscourseTopicVoting::CategorySetting.create!(category: voting_category)
SiteSetting.topic_voting_show_who_voted = true
DiscourseTopicVoting::Vote.create!(user: admin, topic: voting_topic1)
voting_topic1.update_vote_count
end
it "shows voter avatars in the popup" do
sign_in(admin)
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
find(".title-voting .voting-wrapper__count").click
expect(page).to have_css(".voting-voters__list")
expect(page).to have_css(".voting-voters__avatar", count: 1)
end
it "shows empty state when no votes" do
DiscourseTopicVoting::Vote.where(topic: voting_topic1).destroy_all
voting_topic1.update_vote_count
sign_in(admin)
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
find(".title-voting .voting-wrapper__count").click
expect(page).to have_css(".voting-voters__empty")
end
it "uses the topic vote count for overflow text while only loading preview rows" do
stub_const(DiscourseTopicVoting, "VOTER_PREVIEW_LIMIT", 10) do
Fabricate
.times(11, :user)
.each { |user| DiscourseTopicVoting::Vote.create!(user: user, topic: voting_topic1) }
voting_topic1.update_vote_count
sign_in(admin)
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
find(".title-voting .voting-wrapper__count").click
expect(page).to have_css(".voting-voters__avatar", count: 10)
expect(page).to have_css(".voting-voters__overflow", text: "and 2 more...")
end
end
end
context "when viewing navigation tooltips" do
before { DiscourseTopicVoting::CategorySetting.create!(category: voting_category) }
it "shows custom tooltips in voting categories" do
category_page.visit(voting_category)
hot_item = find("#navigation-bar .nav-item_hot")
votes_item = find("#navigation-bar .nav-item_votes")
expect(hot_item["title"]).to eq(I18n.t("js.topic_voting.hot_nav_help"))
expect(votes_item["title"]).to eq(I18n.t("js.filters.votes.help"))
end
it "shows default Hot tooltip in non-voting categories" do
category_page.visit(category1)
hot_item = find("#navigation-bar .nav-item_hot")
expect(hot_item["title"]).to eq(I18n.t("js.filters.hot.help"))
end
end
context "when vote limits are disabled" do
fab!(:voting_post) { Fabricate(:post, topic: voting_topic1) }
fab!(:voting_post2) { Fabricate(:post, topic: voting_topic2) }
before do
DiscourseTopicVoting::CategorySetting.create!(category: voting_category)
SiteSetting.topic_voting_enable_vote_limits = false
SiteSetting.topic_voting_tl4_vote_limit = 1
end
it "allows voting past the TL limit and hides limit UI" do
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
topic_page.vote
expect(topic_page.vote_count).to have_text("1")
expect(topic_page).to have_voted
visit("/t/#{voting_topic2.slug}/#{voting_topic2.id}")
topic_page.vote
expect(topic_page.vote_count).to have_text("1")
end
it "allows removing a vote" do
DiscourseTopicVoting::Vote.create!(user: admin, topic: voting_topic1)
voting_topic1.update_vote_count
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
expect(topic_page.vote_count).to have_text("1")
topic_page.remove_vote
expect(topic_page.vote_count).to have_text("0")
end
it "still offers the watch notification prompt after voting" do
visit("/t/#{voting_topic1.slug}/#{voting_topic1.id}")
topic_page.vote
expect(topic_page.vote_count).to have_text("1")
expect(topic_page).to have_watch_toggle_off
expect(page).to have_no_css(".see-votes")
topic_page.click_watch_toggle
expect(topic_page).to have_watch_toggle_on
expect(TopicUser.find_by(user: admin, topic: voting_topic1).notification_level).to eq(
TopicUser.notification_levels[:watching],
)
end
end
end