require 'rails_helper' require 'topic_view' describe TopicQuery do let!(:user) { Fabricate(:coding_horror) } let(:creator) { Fabricate(:user) } let(:topic_query) { TopicQuery.new(user) } let(:moderator) { Fabricate(:moderator) } let(:admin) { Fabricate(:admin) } context 'secure category' do it "filters categories out correctly" do category = Fabricate(:category) group = Fabricate(:group) category.set_permissions(group => :full) category.save topic = Fabricate(:topic, category: category) topic = Fabricate(:topic, visible: false) expect(TopicQuery.new(nil).list_latest.topics.count).to eq(0) expect(TopicQuery.new(user).list_latest.topics.count).to eq(0) expect(Topic.top_viewed(10).count).to eq(0) expect(Topic.recent(10).count).to eq(0) # mods can see hidden topics expect(TopicQuery.new(moderator).list_latest.topics.count).to eq(1) # admins can see all the topics expect(TopicQuery.new(admin).list_latest.topics.count).to eq(3) group.add(user) group.save expect(TopicQuery.new(user).list_latest.topics.count).to eq(2) end end context "list_topics_by" do it "allows users to view their own invisible topics" do _topic = Fabricate(:topic, user: user) _invisible_topic = Fabricate(:topic, user: user, visible: false) expect(TopicQuery.new(nil).list_topics_by(user).topics.count).to eq(1) expect(TopicQuery.new(user).list_topics_by(user).topics.count).to eq(2) end end context 'bookmarks' do it "filters and returns bookmarks correctly" do post = Fabricate(:post) reply = Fabricate(:post, topic_id: post.topic_id) post2 = Fabricate(:post) PostAction.act(user, post, PostActionType.types[:bookmark]) PostAction.act(user, reply, PostActionType.types[:bookmark]) TopicUser.change(user, post.topic, notification_level: 1) TopicUser.change(user, post2.topic, notification_level: 1) query = TopicQuery.new(user, filter: 'bookmarked').list_latest expect(query.topics.length).to eq(1) expect(query.topics.first.user_data.post_action_data).to eq({PostActionType.types[:bookmark] => [1,2]}) end end context 'deleted filter' do it "filters deleted topics correctly" do _topic = Fabricate(:topic, deleted_at: 1.year.ago) expect(TopicQuery.new(admin, status: 'deleted').list_latest.topics.size).to eq(1) expect(TopicQuery.new(moderator, status: 'deleted').list_latest.topics.size).to eq(1) expect(TopicQuery.new(user, status: 'deleted').list_latest.topics.size).to eq(0) expect(TopicQuery.new(nil, status: 'deleted').list_latest.topics.size).to eq(0) end end context 'category filter' do let(:category) { Fabricate(:category) } let(:diff_category) { Fabricate(:diff_category) } it "returns topics in the category when we filter to it" do expect(TopicQuery.new(moderator).list_latest.topics.size).to eq(0) # Filter by slug expect(TopicQuery.new(moderator, category: category.slug).list_latest.topics.size).to eq(1) expect(TopicQuery.new(moderator, category: "#{category.id}-category").list_latest.topics.size).to eq(1) list = TopicQuery.new(moderator, category: diff_category.slug).list_latest expect(list.topics.size).to eq(1) expect(list.preload_key).to eq("topic_list_c/different-category/l/latest") # Defaults to no category filter when slug does not exist expect(TopicQuery.new(moderator, category: 'made up slug').list_latest.topics.size).to eq(2) end context 'subcategories' do let!(:subcategory) { Fabricate(:category, parent_category_id: category.id)} it "works with subcategories" do expect(TopicQuery.new(moderator, category: category.id).list_latest.topics.size).to eq(1) expect(TopicQuery.new(moderator, category: subcategory.id).list_latest.topics.size).to eq(1) expect(TopicQuery.new(moderator, category: category.id, no_subcategories: true).list_latest.topics.size).to eq(1) end end end context 'tag filter' do let(:tag) { Fabricate(:tag) } let(:other_tag) { Fabricate(:tag) } before do SiteSetting.tagging_enabled = true end context "no category filter" do # create some topics before each test: let!(:tagged_topic1) { Fabricate(:topic, {tags: [tag]}) } let!(:tagged_topic2) { Fabricate(:topic, {tags: [other_tag]}) } let!(:tagged_topic3) { Fabricate(:topic, {tags: [tag, other_tag]}) } let!(:no_tags_topic) { Fabricate(:topic) } it "returns topics with the tag when filtered to it" do expect(TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort) expect(TopicQuery.new(moderator, tags: [tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort) two_tag_topic = TopicQuery.new(moderator, tags: [tag.name]).list_latest.topics.find { |t| t.id == tagged_topic3.id } expect(two_tag_topic.tags.size).to eq(2) # topics with ANY of the given tags: expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort) expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic2.id, tagged_topic3.id].sort) # TODO: topics with ALL of the given tags: # expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort) # expect(TopicQuery.new(moderator, tags: [tag.id, other_tag.id]).list_latest.topics.map(&:id)).to eq([tagged_topic3.id].sort) end it "can return topics with all specified tags" do expect(TopicQuery.new(moderator, tags: [tag.name, other_tag.name], match_all_tags: true).list_latest.topics.map(&:id)).to eq([tagged_topic3.id]) end it "returns an empty relation when an invalid tag is passed" do expect(TopicQuery.new(moderator, tags: [tag.name, 'notatag'], match_all_tags: true).list_latest.topics).to be_empty end it "can return topics with no tags" do expect(TopicQuery.new(moderator, no_tags: true).list_latest.topics.map(&:id)).to eq([no_tags_topic.id]) end end context "and categories too" do let(:category1) { Fabricate(:category) } let(:category2) { Fabricate(:category) } it "returns topics in the given category with the given tag" do tagged_topic1 = Fabricate(:topic, {category: category1, tags: [tag]}) tagged_topic2 = Fabricate(:topic, {category: category2, tags: [tag]}) tagged_topic3 = Fabricate(:topic, {category: category1, tags: [tag, other_tag]}) no_tags_topic = Fabricate(:topic, {category: category1}) expect(TopicQuery.new(moderator, category: category1.id, tags: [tag.name]).list_latest.topics.map(&:id).sort).to eq([tagged_topic1.id, tagged_topic3.id].sort) expect(TopicQuery.new(moderator, category: category2.id, tags: [other_tag.name]).list_latest.topics.size).to eq(0) end end end context 'muted categories' do it 'is removed from new and latest lists' do category = Fabricate(:category) topic = Fabricate(:topic, category: category) CategoryUser.create!(user_id: user.id, category_id: category.id, notification_level: CategoryUser.notification_levels[:muted]) expect(topic_query.list_new.topics.map(&:id)).not_to include(topic.id) expect(topic_query.list_latest.topics.map(&:id)).not_to include(topic.id) end end context 'muted tags' do it 'is removed from new and latest lists' do SiteSetting.tagging_enabled = true SiteSetting.remove_muted_tags_from_latest = true muted_tag, other_tag = Fabricate(:tag), Fabricate(:tag) muted_topic = Fabricate(:topic, tags: [muted_tag]) tagged_topic = Fabricate(:topic, tags: [other_tag]) untagged_topic = Fabricate(:topic) TagUser.create!(user_id: user.id, tag_id: muted_tag.id, notification_level: CategoryUser.notification_levels[:muted]) topic_ids = topic_query.list_latest.topics.map(&:id) expect(topic_ids).not_to include(muted_topic.id) expect(topic_ids).to include(tagged_topic.id) expect(topic_ids).to include(untagged_topic.id) topic_ids = topic_query.list_new.topics.map(&:id) expect(topic_ids).not_to include(muted_topic.id) expect(topic_ids).to include(tagged_topic.id) expect(topic_ids).to include(untagged_topic.id) end end context 'a bunch of topics' do let!(:regular_topic) do Fabricate(:topic, title: 'this is a regular topic', user: creator, views: 100, like_count: 66, posts_count: 3, participant_count: 11, bumped_at: 15.minutes.ago) end let!(:pinned_topic) do Fabricate(:topic, title: 'this is a pinned topic', user: creator, views: 10, like_count: 100, posts_count: 5, participant_count: 12, pinned_at: 10.minutes.ago, pinned_globally: true, bumped_at: 10.minutes.ago) end let!(:archived_topic) do Fabricate(:topic, title: 'this is an archived topic', user: creator, views: 50, like_count: 30, posts_count: 4, archived: true, participant_count: 1, bumped_at: 6.minutes.ago) end let!(:invisible_topic) do Fabricate(:topic, title: 'this is an invisible topic', user: creator, views: 1, like_count: 5, posts_count: 2, visible: false, participant_count: 3, bumped_at: 5.minutes.ago) end let!(:closed_topic) do Fabricate(:topic, title: 'this is a closed topic', user: creator, views: 2, like_count: 1, posts_count: 1, closed: true, participant_count: 2, bumped_at: 1.minute.ago) end let!(:future_topic) do Fabricate(:topic, title: 'this is a topic in far future', user: creator, views: 30, like_count: 11, posts_count: 6, participant_count: 5, bumped_at: 1000.years.from_now) end let(:topics) { topic_query.list_latest.topics } context 'list_latest' do it "returns the topics in the correct order" do expect(topics.map(&:id)).to eq([pinned_topic, future_topic, closed_topic, archived_topic, regular_topic].map(&:id)) # includes the invisible topic if you're a moderator expect(TopicQuery.new(moderator).list_latest.topics.include?(invisible_topic)).to eq(true) # includes the invisible topic if you're an admin" do expect(TopicQuery.new(admin).list_latest.topics.include?(invisible_topic)).to eq(true) end context 'sort_order' do def ids_in_order(order, descending=true) TopicQuery.new(admin, order: order, ascending: descending ? 'false' : 'true').list_latest.topics.map(&:id) end it "returns the topics in correct order" do # returns the topics in likes order if requested expect(ids_in_order('posts')).to eq([future_topic, pinned_topic, archived_topic, regular_topic, invisible_topic, closed_topic].map(&:id)) # returns the topics in reverse likes order if requested expect(ids_in_order('posts', false)).to eq([closed_topic, invisible_topic, regular_topic, archived_topic, pinned_topic, future_topic].map(&:id)) # returns the topics in likes order if requested expect(ids_in_order('likes')).to eq([pinned_topic, regular_topic, archived_topic, future_topic, invisible_topic, closed_topic].map(&:id)) # returns the topics in reverse likes order if requested expect(ids_in_order('likes', false)).to eq([closed_topic, invisible_topic, future_topic, archived_topic, regular_topic, pinned_topic].map(&:id)) # returns the topics in views order if requested expect(ids_in_order('views')).to eq([regular_topic, archived_topic, future_topic, pinned_topic, closed_topic, invisible_topic].map(&:id)) # returns the topics in reverse views order if requested" do expect(ids_in_order('views', false)).to eq([invisible_topic, closed_topic, pinned_topic, future_topic, archived_topic, regular_topic].map(&:id)) # returns the topics in posters order if requested" do expect(ids_in_order('posters')).to eq([pinned_topic, regular_topic, future_topic, invisible_topic, closed_topic, archived_topic].map(&:id)) # returns the topics in reverse posters order if requested" do expect(ids_in_order('posters', false)).to eq([archived_topic, closed_topic, invisible_topic, future_topic, regular_topic, pinned_topic].map(&:id)) # sets a custom field for each topic to emulate a plugin regular_topic.custom_fields["sheep"] = 26 pinned_topic.custom_fields["sheep"] = 47 archived_topic.custom_fields["sheep"] = 69 invisible_topic.custom_fields["sheep"] = 12 closed_topic.custom_fields["sheep"] = 31 future_topic.custom_fields["sheep"] = 53 regular_topic.save pinned_topic.save archived_topic.save invisible_topic.save closed_topic.save future_topic.save # adds the custom field as a viable sort option class ::TopicQuery SORTABLE_MAPPING["sheep"] = "custom_fields.sheep" end # returns the topics in the sheep order if requested" do expect(ids_in_order('sheep')).to eq([archived_topic, future_topic, pinned_topic, closed_topic, regular_topic, invisible_topic].map(&:id)) # returns the topics in reverse sheep order if requested" do expect(ids_in_order('sheep', false)).to eq([invisible_topic, regular_topic, closed_topic, pinned_topic, future_topic, archived_topic].map(&:id)) end end end context 'after clearring a pinned topic' do before do pinned_topic.clear_pin_for(user) end it "no longer shows the pinned topic at the top" do expect(topics).to eq([future_topic, closed_topic, archived_topic, pinned_topic, regular_topic]) end end end context 'categorized' do let(:category) { Fabricate(:category) } let(:topic_category) { category.topic } let!(:topic_no_cat) { Fabricate(:topic) } let!(:topic_in_cat1) { Fabricate(:topic, category: category, bumped_at: 10.minutes.ago, created_at: 10.minutes.ago) } let!(:topic_in_cat2) { Fabricate(:topic, category: category) } describe '#list_new_in_category' do it 'returns the topic category and the categorized topic' do expect( topic_query.list_new_in_category(category).topics.map(&:id) ).to eq([topic_in_cat2.id, topic_category.id, topic_in_cat1.id]) end end end context 'unread / read topics' do context 'with no data' do it "has no unread topics" do expect(topic_query.list_unread.topics).to be_blank end end context 'with read data' do let!(:partially_read) { Fabricate(:post, user: creator).topic } let!(:fully_read) { Fabricate(:post, user: creator).topic } before do TopicUser.update_last_read(user, partially_read.id, 0, 0) TopicUser.update_last_read(user, fully_read.id, 1, 0) end context 'list_unread' do it 'contains no topics' do expect(topic_query.list_unread.topics).to eq([]) end end context 'user with auto_track_topics list_unread' do before do user.user_option.auto_track_topics_after_msecs = 0 user.user_option.save end it 'only contains the partially read topic' do expect(topic_query.list_unread.topics).to eq([partially_read]) end end context 'list_read' do it 'contain both topics ' do expect(topic_query.list_read.topics).to match_array([fully_read, partially_read]) end end end end context 'list_new' do context 'without a new topic' do it "has no new topics" do expect(topic_query.list_new.topics).to be_blank end end context 'preload api' do let(:topics) { } it "preloads data correctly" do TopicList.preloaded_custom_fields << "tag" TopicList.preloaded_custom_fields << "age" TopicList.preloaded_custom_fields << "foo" topic = Fabricate.build(:topic, user: creator, bumped_at: 10.minutes.ago) topic.custom_fields["tag"] = ["a","b","c"] topic.custom_fields["age"] = 22 topic.save new_topic = topic_query.list_new.topics.first expect(new_topic.custom_fields["tag"].sort).to eq(["a","b","c"]) expect(new_topic.custom_fields["age"]).to eq("22") expect(new_topic.custom_field_preloaded?("tag")).to eq(true) expect(new_topic.custom_field_preloaded?("age")).to eq(true) expect(new_topic.custom_field_preloaded?("foo")).to eq(true) expect(new_topic.custom_field_preloaded?("bar")).to eq(false) TopicList.preloaded_custom_fields.clear # if we attempt to access non preloaded fields explode expect{new_topic.custom_fields["boom"]}.to raise_error(StandardError) end end context 'with a new topic' do let!(:new_topic) { Fabricate(:topic, user: creator, bumped_at: 10.minutes.ago) } let(:topics) { topic_query.list_new.topics } it "contains no new topics for a user that has missed the window" do expect(topic_query.list_new.topics).to eq([new_topic]) user.user_option.new_topic_duration_minutes = 5 user.user_option.save new_topic.created_at = 10.minutes.ago new_topic.save expect(topic_query.list_new.topics).to eq([]) end context "muted topics" do before do new_topic.notify_muted!(user) end it "returns an empty set" do expect(topics).to be_blank expect(topic_query.list_latest.topics).to be_blank end context 'un-muted' do before do new_topic.notify_tracking!(user) end it "returns the topic again" do expect(topics).to eq([new_topic]) expect(topic_query.list_latest.topics).not_to be_blank end end end end end context 'list_posted' do let(:topics) { topic_query.list_posted.topics } it "returns blank when there are no posted topics" do expect(topics).to be_blank end context 'created topics' do let!(:created_topic) { create_post(user: user).topic } it "includes the created topic" do expect(topics.include?(created_topic)).to eq(true) end end context "topic you've posted in" do let(:other_users_topic) { create_post(user: creator).topic } let!(:your_post) { create_post(user: user, topic: other_users_topic )} it "includes the posted topic" do expect(topics.include?(other_users_topic)).to eq(true) end end context "topic you haven't posted in" do let(:other_users_topic) { create_post(user: creator).topic } it "does not include the topic" do expect(topics).to be_blank end context "but interacted with" do it "is not included if read" do TopicUser.update_last_read(user, other_users_topic.id, 0, 0) expect(topics).to be_blank end it "is not included if muted" do other_users_topic.notify_muted!(user) expect(topics).to be_blank end it "is not included if tracking" do other_users_topic.notify_tracking!(user) expect(topics).to be_blank end end end end context 'suggested_for message do' do let(:user) do Fabricate(:admin) end let(:sender) do Fabricate(:admin) end let(:group_with_user) do group = Fabricate(:group) group.add(user) group.save group end def create_pm(user, opts=nil) unless opts opts = user user = nil end create_post(opts.merge(user: user, archetype: Archetype.private_message)).topic end def read(user,topic,post_number) TopicUser.update_last_read(user, topic, post_number, 10000) end it 'returns the correct suggestions' do pm_to_group = create_pm(sender, target_group_names: [group_with_user.name]) pm_to_user = create_pm(sender, target_usernames: [user.username]) new_pm = create_pm(target_usernames: [user.username]) unread_pm = create_pm(target_usernames: [user.username]) read(user,unread_pm, 0) old_unrelated_pm = create_pm(target_usernames: [user.username]) read(user, old_unrelated_pm, 1) related_by_user_pm = create_pm(sender, target_usernames: [user.username]) read(user, related_by_user_pm, 1) related_by_group_pm = create_pm(sender, target_group_names: [group_with_user.name]) read(user, related_by_group_pm, 1) expect(TopicQuery.new(user).list_suggested_for(pm_to_group).topics.map(&:id)).to( eq([related_by_group_pm.id, related_by_user_pm.id, pm_to_user.id]) ) expect(TopicQuery.new(user).list_suggested_for(pm_to_user).topics.map(&:id)).to( eq([new_pm.id, unread_pm.id, related_by_user_pm.id]) ) end end context 'suggested_for' do before do RandomTopicSelector.clear_cache! end context 'when anonymous' do let(:topic) { Fabricate(:topic) } let!(:new_topic) { Fabricate(:post, user: creator).topic } it "should return the new topic" do expect(TopicQuery.new.list_suggested_for(topic).topics).to eq([new_topic]) end end context "anonymously browsing with invisible, closed and archived" do let!(:topic) { Fabricate(:topic) } let!(:regular_topic) { Fabricate(:post, user: creator).topic } let!(:closed_topic) { Fabricate(:topic, user: creator, closed: true) } let!(:archived_topic) { Fabricate(:topic, user: creator, archived: true) } let!(:invisible_topic) { Fabricate(:topic, user: creator, visible: false) } it "should omit the closed/archived/invisbiel topics from suggested" do expect(TopicQuery.new.list_suggested_for(topic).topics).to eq([regular_topic]) end end context 'when logged in' do let(:topic) { Fabricate(:topic) } let(:suggested_topics) { tt = topic # lets clear cache once category is created - working around caching is hard RandomTopicSelector.clear_cache! topic_query.list_suggested_for(tt).topics.map{|t| t.id} } it "should return empty results when there is nothing to find" do expect(suggested_topics).to be_blank end context 'random suggested' do let!(:new_topic) { Fabricate(:topic, created_at: 2.days.ago) } let!(:old_topic) { Fabricate(:topic, created_at: 3.years.ago) } it 'respects suggested_topics_max_days_old' do SiteSetting.suggested_topics_max_days_old = 1365 tt = topic RandomTopicSelector.clear_cache! expect(topic_query.list_suggested_for(tt).topics.length).to eq(2) SiteSetting.suggested_topics_max_days_old = 365 RandomTopicSelector.clear_cache! expect(topic_query.list_suggested_for(tt).topics.length).to eq(1) end end context 'with some existing topics' do let!(:partially_read) { Fabricate(:post, user: creator).topic } let!(:new_topic) { Fabricate(:post, user: creator).topic } let!(:fully_read) { Fabricate(:post, user: creator).topic } let!(:closed_topic) { Fabricate(:topic, user: creator, closed: true) } let!(:archived_topic) { Fabricate(:topic, user: creator, archived: true) } let!(:invisible_topic) { Fabricate(:topic, user: creator, visible: false) } let!(:fully_read_closed) { Fabricate(:post, user: creator).topic } let!(:fully_read_archived) { Fabricate(:post, user: creator).topic } before do user.user_option.auto_track_topics_after_msecs = 0 user.user_option.save TopicUser.update_last_read(user, partially_read.id, 0, 0) TopicUser.update_last_read(user, fully_read.id, 1, 0) TopicUser.update_last_read(user, fully_read_closed.id, 1, 0) TopicUser.update_last_read(user, fully_read_archived.id, 1, 0) fully_read_closed.closed = true fully_read_closed.save fully_read_archived.archived = true fully_read_archived.save end it "returns unread, then new, then random" do SiteSetting.suggested_topics = 7 expect(suggested_topics[0]).to eq(partially_read.id) expect(suggested_topics[1,3]).to include(new_topic.id) expect(suggested_topics[1,3]).to include(closed_topic.id) expect(suggested_topics[1,3]).to include(archived_topic.id) # The line below appears to randomly fail, no idea why need to restructure test #expect(suggested_topics[4]).to eq(fully_read.id) # random doesn't include closed and archived end it "won't return new or fully read if there are enough partially read topics" do SiteSetting.suggested_topics = 1 expect(suggested_topics).to eq([partially_read.id]) end it "won't return fully read if there are enough partially read topics and new topics" do SiteSetting.suggested_topics = 4 expect(suggested_topics[0]).to eq(partially_read.id) expect(suggested_topics[1,3]).to include(new_topic.id) expect(suggested_topics[1,3]).to include(closed_topic.id) expect(suggested_topics[1,3]).to include(archived_topic.id) end end end end end