diff --git a/app/assets/javascripts/discourse/controllers/list_controller.js b/app/assets/javascripts/discourse/controllers/list_controller.js index 8282101fa73a6..b69e37ede0c16 100644 --- a/app/assets/javascripts/discourse/controllers/list_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_controller.js @@ -35,7 +35,7 @@ Discourse.ListController = Discourse.Controller.extend({ var listController = this; this.set('loading', true); - var trackingState = Discourse.get('currentUser.userTrackingState'); + var trackingState = Discourse.TopicTrackingState.current(); if (filterMode === 'categories') { return Discourse.CategoryList.list(filterMode).then(function(items) { diff --git a/app/assets/javascripts/discourse/controllers/list_topics_controller.js b/app/assets/javascripts/discourse/controllers/list_topics_controller.js index 3cf53a7de375f..90425fe1a35cc 100644 --- a/app/assets/javascripts/discourse/controllers/list_topics_controller.js +++ b/app/assets/javascripts/discourse/controllers/list_topics_controller.js @@ -55,7 +55,7 @@ Discourse.ListTopicsController = Discourse.ObjectController.extend({ // Show newly inserted topics showInserted: function(e) { - var tracker = Discourse.get('currentUser.userTrackingState'); + var tracker = Discourse.TopicTrackingState.current(); // Move inserted into topics this.get('content').loadBefore(tracker.get('newIncoming')); diff --git a/app/assets/javascripts/discourse/models/nav_item.js b/app/assets/javascripts/discourse/models/nav_item.js index fa92786089d38..4b1e9e28e3b53 100644 --- a/app/assets/javascripts/discourse/models/nav_item.js +++ b/app/assets/javascripts/discourse/models/nav_item.js @@ -10,7 +10,10 @@ var validNavNames = ['latest', 'hot', 'categories', 'category', 'favorited', 'un var validAnon = ['latest', 'hot', 'categories', 'category']; Discourse.NavItem = Discourse.Model.extend({ - userTrackingStateBinding: Ember.Binding.oneWay('Discourse.currentUser.userTrackingState.messageCount'), + topicTrackingState: function(){ + return Discourse.TopicTrackingState.current(); + }.property(), + categoryName: function() { var split = this.get('name').split('/'); return split[0] === 'category' ? split[1] : null; @@ -25,11 +28,11 @@ Discourse.NavItem = Discourse.Model.extend({ }.property('name'), count: function() { - var state = Discourse.get('currentUser.userTrackingState'); + var state = this.get('topicTrackingState'); if (state) { return state.lookupCount(this.get('name')); } - }.property('userTrackingState') + }.property('topicTrackingState.messageCount') }); Discourse.NavItem.reopenClass({ diff --git a/app/assets/javascripts/discourse/models/user_tracking_state.js b/app/assets/javascripts/discourse/models/topic_tracking_state.js similarity index 79% rename from app/assets/javascripts/discourse/models/user_tracking_state.js rename to app/assets/javascripts/discourse/models/topic_tracking_state.js index a0297ad20626e..f0420844b3058 100644 --- a/app/assets/javascripts/discourse/models/user_tracking_state.js +++ b/app/assets/javascripts/discourse/models/topic_tracking_state.js @@ -1,4 +1,4 @@ -Discourse.UserTrackingState = Discourse.Model.extend({ +Discourse.TopicTrackingState = Discourse.Model.extend({ messageCount: 0, init: function(){ @@ -17,7 +17,7 @@ Discourse.UserTrackingState = Discourse.Model.extend({ tracker.removeTopic(data.topic_id); } - if (data.message_type === "new_topic") { + if (data.message_type === "new_topic" || data.message_type === "unread") { tracker.states["t" + data.topic_id] = data.payload; tracker.notify(data); } @@ -26,7 +26,10 @@ Discourse.UserTrackingState = Discourse.Model.extend({ }; Discourse.MessageBus.subscribe("/new", process); - Discourse.MessageBus.subscribe("/unread/" + Discourse.currentUser.id, process); + var currentUser = Discourse.User.current(); + if(currentUser) { + Discourse.MessageBus.subscribe("/unread/" + currentUser.id, process); + } }, notify: function(data){ @@ -62,6 +65,8 @@ Discourse.UserTrackingState = Discourse.Model.extend({ sync: function(list, filter){ var tracker = this; + if(!list || !list.topics || !list.topics.length) { return; } + if(filter === "new" && !list.more_topics_url){ // scrub all new rows and reload from list $.each(this.states, function(){ @@ -88,8 +93,10 @@ Discourse.UserTrackingState = Discourse.Model.extend({ if(topic.unseen) { row.last_read_post_number = null; } else { - row.last_read_post_number = topic.last_read_post_number; + // subtle issue here + row.last_read_post_number = topic.last_read_post_number || topic.highest_post_number; } + row.highest_post_number = topic.highest_post_number; if (topic.category) { row.category_name = topic.category.name; @@ -97,6 +104,8 @@ Discourse.UserTrackingState = Discourse.Model.extend({ if (row.last_read_post_number === null || row.highest_post_number > row.last_read_post_number) { tracker.states["t" + topic.id] = row; + } else { + delete tracker.states["t" + topic.id]; } }); @@ -151,18 +160,28 @@ Discourse.UserTrackingState = Discourse.Model.extend({ // not exposed var states = this.states; - data.each(function(row){ - states["t" + row.topic_id] = row; - }); + if(data) { + data.each(function(row){ + states["t" + row.topic_id] = row; + }); + } } }); -Discourse.UserTrackingState.reopenClass({ +Discourse.TopicTrackingState.reopenClass({ createFromStates: function(data){ - var instance = Discourse.UserTrackingState.create(); + var instance = Discourse.TopicTrackingState.create(); instance.loadStates(data); instance.establishChannels(); return instance; + }, + current: function(){ + if (!this.tracker) { + var data = PreloadStore.get('topicTrackingStates'); + this.tracker = this.createFromStates(data); + PreloadStore.remove('topicTrackingStates'); + } + return this.tracker; } }); diff --git a/app/assets/javascripts/discourse/routes/application_route.js b/app/assets/javascripts/discourse/routes/application_route.js deleted file mode 100644 index 337f3df6ec688..0000000000000 --- a/app/assets/javascripts/discourse/routes/application_route.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - The base Application route - - @class ApplicationRoute - @extends Discourse.Route - @namespace Discourse - @module Discourse -**/ -Discourse.ApplicationRoute = Discourse.Route.extend({ - setupController: function(controller) { - Discourse.set('site', Discourse.Site.create(PreloadStore.get('site'))); - var currentUser = PreloadStore.get('currentUser'); - if (currentUser) { - var states = currentUser.user_tracking_states; - currentUser.user_tracking_states = null; - - Discourse.set('currentUser', Discourse.User.create(currentUser)); - Discourse.set('currentUser.userTrackingState', Discourse.UserTrackingState.createFromStates(states)); - } - // make sure we delete preloaded data - PreloadStore.remove('site'); - PreloadStore.remove('currentUser'); - } -}); diff --git a/app/assets/javascripts/discourse/templates/list/topics.js.handlebars b/app/assets/javascripts/discourse/templates/list/topics.js.handlebars index 7ad5eaa802e99..6ecb51f44c20c 100644 --- a/app/assets/javascripts/discourse/templates/list/topics.js.handlebars +++ b/app/assets/javascripts/discourse/templates/list/topics.js.handlebars @@ -28,12 +28,12 @@ - {{#if Discourse.currentUser.userTrackingState.hasIncoming}} + {{#if view.topicTrackingState.hasIncoming}}
- {{countI18n new_topics_inserted countBinding="Discourse.currentUser.userTrackingState.incomingCount"}} + {{countI18n new_topics_inserted countBinding="view.topicTrackingState.incomingCount"}} {{i18n show_new_topics}}
diff --git a/app/assets/javascripts/discourse/views/list/list_topics_view.js b/app/assets/javascripts/discourse/views/list/list_topics_view.js index 676bab9f00adc..f48d3d82451f0 100644 --- a/app/assets/javascripts/discourse/views/list/list_topics_view.js +++ b/app/assets/javascripts/discourse/views/list/list_topics_view.js @@ -14,6 +14,9 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, { listBinding: 'controller.model', loadedMore: false, currentTopicId: null, + topicTrackingState: function() { + return Discourse.TopicTrackingState.current(); + }.property(), willDestroyElement: function() { this.unbindScrolling(); @@ -42,8 +45,11 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.Scrolling, { }, showTable: function() { - return this.get('list.topics').length > 0 || Discourse.get('currentUser.userTrackingState.hasIncoming'); - }.property('list.topics','Discourse.currentUser.userTrackingState.hasIncoming'), + var topics = this.get('list.topics'); + if(topics) { + return this.get('list.topics').length > 0 || this.get('topicTrackingState.hasIncoming'); + } + }.property('list.topics','topicTrackingState.hasIncoming'), loadMore: function() { var listTopicsView = this; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 638f605484a2f..37d53f3c55cd6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -107,6 +107,9 @@ def preload_json if current_user.present? store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, root: false))) + + serializer = ActiveModel::ArraySerializer.new(TopicTrackingState.report([current_user.id]), each_serializer: TopicTrackingStateSerializer) + store_preloaded("topicTrackingStates", MultiJson.dump(serializer)) end store_preloaded("siteSettings", SiteSetting.client_settings_json) end diff --git a/app/models/category.rb b/app/models/category.rb index bdeacedb57d6a..971d5d0fb5879 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -133,6 +133,12 @@ def allow(group) end end + def secure_group_ids + if self.secure + groups.pluck("groups.id") + end + end + end # == Schema Information diff --git a/app/models/user_tracking_state.rb b/app/models/topic_tracking_state.rb similarity index 67% rename from app/models/user_tracking_state.rb rename to app/models/topic_tracking_state.rb index 50c8f8fce5602..8239f717a6cef 100644 --- a/app/models/user_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -3,7 +3,7 @@ # the allows end users to always know which topics have unread posts in them # and which topics are new -class UserTrackingState +class TopicTrackingState include ActiveModel::SerializerSupport @@ -11,16 +11,53 @@ class UserTrackingState attr_accessor :user_id, :topic_id, :highest_post_number, :last_read_post_number, :created_at, :category_name - MessageBus.client_filter(CHANNEL) do |user_id, message| - if user_id - UserTrackingState.new(User.find(user_id)).filter(message) - else - nil + def self.publish_new(topic) + + message = { + topic_id: topic.id, + message_type: "new_topic", + payload: { + last_read_post_number: nil, + highest_post_number: 1, + created_at: topic.created_at, + topic_id: topic.id + } + } + + group_ids = topic.category && topic.category.secure_group_ids + + MessageBus.publish("/new", message.as_json, group_ids: group_ids) + publish_read(topic.id, 1, topic.user_id) + end + + def self.publish_unread(post) + # TODO at high scale we are going to have to defer this, + # perhaps cut down to users that are around in the last 7 days as well + # + group_ids = post.topic.category && post.topic.category.secure_group_ids + + TopicUser + .tracking(post.topic_id) + .select([:user_id,:last_read_post_number]) + .each do |tu| + + message = { + topic_id: post.topic_id, + message_type: "unread", + payload: { + last_read_post_number: tu.last_read_post_number, + highest_post_number: post.post_number, + created_at: post.created_at, + topic_id: post.topic_id + } + } + + MessageBus.publish("/unread/#{tu.user_id}", message.as_json, group_ids: group_ids) + end end - def self.trigger_change(topic_id, post_number, user_id=nil) - MessageBus.publish(CHANNEL, "CHANGE", user_ids: [user_id].compact) + def self.publish_read(topic_id, highest_post_number, user_id) end def self.treat_as_new_topic_clause @@ -76,7 +113,7 @@ def self.report(user_ids, topic_id = nil) end SqlBuilder.new(sql) - .map_exec(UserTrackingState, user_ids: user_ids, topic_id: topic_id) + .map_exec(TopicTrackingState, user_ids: user_ids, topic_id: topic_id) end diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb index e0d98d8be420c..e32a13bdf6bfe 100644 --- a/app/models/topic_user.rb +++ b/app/models/topic_user.rb @@ -5,6 +5,12 @@ class TopicUser < ActiveRecord::Base scope :starred_since, lambda { |sinceDaysAgo| where('starred_at > ?', sinceDaysAgo.days.ago) } scope :by_date_starred, group('date(starred_at)').order('date(starred_at)') + scope :tracking, lambda { |topic_id| + where(topic_id: topic_id) + .where("COALESCE(topic_users.notification_level, :regular) >= :tracking", + regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking]) + } + # Class methods class << self diff --git a/app/models/user.rb b/app/models/user.rb index 02f84a31ef3aa..7fde363025142 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -576,9 +576,6 @@ def flag_linked_posts_as_spam end end - def user_tracking_states - UserTrackingState.report([self.id]) - end protected diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index dd4747116f99f..d34826f0bc9e7 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -14,10 +14,6 @@ class CurrentUserSerializer < BasicUserSerializer :external_links_in_new_tab, :trust_level - has_many :user_tracking_states, serializer: UserTrackingStateSerializer, embed: :objects - - # we probably want to move this into site, but that json is cached so hanging it off current user seems okish - def include_site_flagged_posts_count? object.staff? end diff --git a/app/serializers/user_tracking_state_serializer.rb b/app/serializers/topic_tracking_state_serializer.rb similarity index 63% rename from app/serializers/user_tracking_state_serializer.rb rename to app/serializers/topic_tracking_state_serializer.rb index 1abd7c08b8e15..b4df77a025051 100644 --- a/app/serializers/user_tracking_state_serializer.rb +++ b/app/serializers/topic_tracking_state_serializer.rb @@ -1,3 +1,3 @@ -class UserTrackingStateSerializer < ApplicationSerializer +class TopicTrackingStateSerializer < ApplicationSerializer attributes :topic_id, :highest_post_number, :last_read_post_number, :created_at, :category_name end diff --git a/lib/post_creator.rb b/lib/post_creator.rb index 6962ef1c36bac..115285488bdfb 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -123,7 +123,6 @@ def create @user.last_posted_at = post.created_at @user.save! - if post.post_number > 1 MessageBus.publish("/topic/#{post.topic_id}",{ id: post.id, @@ -142,12 +141,14 @@ def create post.save_reply_relationships end - # We need to enqueue jobs after the transaction. Otherwise they might begin before the data has - # been comitted. - topic_id = @opts[:topic_id] || topic.try(:id) - Jobs.enqueue(:feature_topic_users, topic_id: topic.id) if topic_id.present? - if post + if post && !post.errors.present? + + # We need to enqueue jobs after the transaction. Otherwise they might begin before the data has + # been comitted. + topic_id = @opts[:topic_id] || topic.try(:id) + Jobs.enqueue(:feature_topic_users, topic_id: topic.id) if topic_id.present? post.trigger_post_process + after_post_create(post) after_topic_create(topic) if new_topic end @@ -164,7 +165,13 @@ def self.create(user, opts) def secure_group_ids(topic) @secure_group_ids ||= if topic.category && topic.category.secure? - topic.category.groups.select("groups.id").map{|g| g.id} + topic.category.secure_group_ids + end + end + + def after_post_create(post) + if post.post_number > 1 + TopicTrackingState.publish_unread(post) end end @@ -177,21 +184,8 @@ def after_topic_create(topic) topic.posters = topic.posters_summary topic.posts_count = 1 - topic_json = TopicListItemSerializer.new(topic).as_json - - message = { - topic_id: topic.id, - message_type: "new_topic", - payload: { - last_read_post_number: nil, - topic_id: topic.id - } - } - - group_ids = secure_group_ids(topic) - MessageBus.publish("/new", message.as_json, group_ids: group_ids) - # TODO post creator should get an unread + TopicTrackingState.publish_new(topic) end def create_topic diff --git a/script/alice.txt b/script/alice.txt new file mode 100644 index 0000000000000..5f3f07427df20 --- /dev/null +++ b/script/alice.txt @@ -0,0 +1,122 @@ +Alice was beginning to get very tired of sitting by her sister on the +bank, and of having nothing to do: once or twice she had peeped into the +book her sister was reading, but it had no pictures or conversations in +it, 'and what is the use of a book,' thought Alice 'without pictures or +conversation?' + +So she was considering in her own mind (as well as she could, for the +hot day made her feel very sleepy and stupid), whether the pleasure +of making a daisy-chain would be worth the trouble of getting up and +picking the daisies, when suddenly a White Rabbit with pink eyes ran +close by her. + +There was nothing so VERY remarkable in that; nor did Alice think it so +VERY much out of the way to hear the Rabbit say to itself, 'Oh dear! +Oh dear! I shall be late!' (when she thought it over afterwards, it +occurred to her that she ought to have wondered at this, but at the time +it all seemed quite natural); but when the Rabbit actually TOOK A WATCH +OUT OF ITS WAISTCOAT-POCKET, and looked at it, and then hurried on, +Alice started to her feet, for it flashed across her mind that she had +never before seen a rabbit with either a waistcoat-pocket, or a watch +to take out of it, and burning with curiosity, she ran across the field +after it, and fortunately was just in time to see it pop down a large +rabbit-hole under the hedge. + +In another moment down went Alice after it, never once considering how +in the world she was to get out again. + +The rabbit-hole went straight on like a tunnel for some way, and then +dipped suddenly down, so suddenly that Alice had not a moment to think +about stopping herself before she found herself falling down a very deep +well. + +Either the well was very deep, or she fell very slowly, for she had +plenty of time as she went down to look about her and to wonder what was +going to happen next. First, she tried to look down and make out what +she was coming to, but it was too dark to see anything; then she +looked at the sides of the well, and noticed that they were filled with +cupboards and book-shelves; here and there she saw maps and pictures +hung upon pegs. She took down a jar from one of the shelves as +she passed; it was labelled 'ORANGE MARMALADE', but to her great +disappointment it was empty: she did not like to drop the jar for fear +of killing somebody, so managed to put it into one of the cupboards as +she fell past it. + +'Well!' thought Alice to herself, 'after such a fall as this, I shall +think nothing of tumbling down stairs! How brave they'll all think me at +home! Why, I wouldn't say anything about it, even if I fell off the top +of the house!' (Which was very likely true.) + +Down, down, down. Would the fall NEVER come to an end! 'I wonder how +many miles I've fallen by this time?' she said aloud. 'I must be getting +somewhere near the centre of the earth. Let me see: that would be four +thousand miles down, I think--' (for, you see, Alice had learnt several +things of this sort in her lessons in the schoolroom, and though this +was not a VERY good opportunity for showing off her knowledge, as there +was no one to listen to her, still it was good practice to say it over) +'--yes, that's about the right distance--but then I wonder what Latitude +or Longitude I've got to?' (Alice had no idea what Latitude was, or +Longitude either, but thought they were nice grand words to say.) + +Presently she began again. 'I wonder if I shall fall right THROUGH the +earth! How funny it'll seem to come out among the people that walk with +their heads downward! The Antipathies, I think--' (she was rather glad +there WAS no one listening, this time, as it didn't sound at all the +right word) '--but I shall have to ask them what the name of the country +is, you know. Please, Ma'am, is this New Zealand or Australia?' (and +she tried to curtsey as she spoke--fancy CURTSEYING as you're falling +through the air! Do you think you could manage it?) 'And what an +ignorant little girl she'll think me for asking! No, it'll never do to +ask: perhaps I shall see it written up somewhere.' + +Down, down, down. There was nothing else to do, so Alice soon began +talking again. 'Dinah'll miss me very much to-night, I should think!' +(Dinah was the cat.) 'I hope they'll remember her saucer of milk at +tea-time. Dinah my dear! I wish you were down here with me! There are no +mice in the air, I'm afraid, but you might catch a bat, and that's very +like a mouse, you know. But do cats eat bats, I wonder?' And here Alice +began to get rather sleepy, and went on saying to herself, in a dreamy +sort of way, 'Do cats eat bats? Do cats eat bats?' and sometimes, 'Do +bats eat cats?' for, you see, as she couldn't answer either question, +it didn't much matter which way she put it. She felt that she was dozing +off, and had just begun to dream that she was walking hand in hand with +Dinah, and saying to her very earnestly, 'Now, Dinah, tell me the truth: +did you ever eat a bat?' when suddenly, thump! thump! down she came upon +a heap of sticks and dry leaves, and the fall was over. + +Alice was not a bit hurt, and she jumped up on to her feet in a moment: +she looked up, but it was all dark overhead; before her was another +long passage, and the White Rabbit was still in sight, hurrying down it. +There was not a moment to be lost: away went Alice like the wind, and +was just in time to hear it say, as it turned a corner, 'Oh my ears +and whiskers, how late it's getting!' She was close behind it when she +turned the corner, but the Rabbit was no longer to be seen: she found +herself in a long, low hall, which was lit up by a row of lamps hanging +from the roof. + +There were doors all round the hall, but they were all locked; and when +Alice had been all the way down one side and up the other, trying every +door, she walked sadly down the middle, wondering how she was ever to +get out again. + +Suddenly she came upon a little three-legged table, all made of solid +glass; there was nothing on it except a tiny golden key, and Alice's +first thought was that it might belong to one of the doors of the hall; +but, alas! either the locks were too large, or the key was too small, +but at any rate it would not open any of them. However, on the second +time round, she came upon a low curtain she had not noticed before, and +behind it was a little door about fifteen inches high: she tried the +little golden key in the lock, and to her great delight it fitted! + +Alice opened the door and found that it led into a small passage, not +much larger than a rat-hole: she knelt down and looked along the passage +into the loveliest garden you ever saw. How she longed to get out of +that dark hall, and wander about among those beds of bright flowers and +those cool fountains, but she could not even get her head through the +doorway; 'and even if my head would go through,' thought poor Alice, 'it +would be of very little use without my shoulders. Oh, how I wish I could +shut up like a telescope! I think I could, if I only know how to begin.' +For, you see, so many out-of-the-way things had happened lately, +that Alice had begun to think that very few things indeed were really +impossible. + diff --git a/script/user_simulator.rb b/script/user_simulator.rb new file mode 100644 index 0000000000000..0005a6ebaec0e --- /dev/null +++ b/script/user_simulator.rb @@ -0,0 +1,60 @@ +# used during local testing, simulates a user active on the site. +# +# by default 1 new topic every 30 sec, 1 reply to last topic every 30 secs + +require 'optparse' +require 'gabbler' + +user_id = nil + +def sentence + @gabbler ||= Gabbler.new.tap do |gabbler| + story = File.read(File.dirname(__FILE__) + "/alice.txt") + gabbler.learn(story) + end + + sentence = "" + until sentence.length > 800 do + sentence << @gabbler.sentence + sentence << "\n" + end + sentence +end + +OptionParser.new do |opts| + opts.banner = "Usage: ruby user_simulator.rb [options]" + opts.on("-u", "--user NUMBER", "user id") do |u| + user_id = u.to_i + end +end.parse! + +unless user_id + puts "user must be specified" + exit +end + +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") + +unless Rails.env.development? + puts "Bad idea to run a script that inserts random posts in any non development environment" + exit +end + + +user = User.find(user_id) +last_topics = Topic.order('id desc').limit(10).pluck(:id) + +puts "Simulating activity for user id #{user.id}: #{user.name}" + + +while true + # puts "Creating a random topi" + + # category = Category.where(secure: false).order('random()').first + # PostCreator.create(user, raw: sentence, title: sentence[0..50].strip, category: category.name) + + puts "creating random reply" + PostCreator.create(user, raw: sentence, topic_id: last_topics.sample) + + sleep 3 +end diff --git a/spec/models/user_tracking_state_spec.rb b/spec/models/topic_tracking_state_spec.rb similarity index 65% rename from spec/models/user_tracking_state_spec.rb rename to spec/models/topic_tracking_state_spec.rb index cb5dd6872cc38..af9348736f71d 100644 --- a/spec/models/user_tracking_state_spec.rb +++ b/spec/models/topic_tracking_state_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UserTrackingState do +describe TopicTrackingState do let(:user) do Fabricate(:user) @@ -10,13 +10,18 @@ Fabricate(:post) end + it "can correctly publish unread" do + # TODO setup stuff and look at messages + TopicTrackingState.publish_unread(post) + end + it "correctly gets the tracking state" do - report = UserTrackingState.report([user.id]) + report = TopicTrackingState.report([user.id]) report.length.should == 0 new_post = post - report = UserTrackingState.report([user.id]) + report = TopicTrackingState.report([user.id]) report.length.should == 1 row = report[0] @@ -27,15 +32,15 @@ row.user_id.should == user.id # lets not leak out random users - UserTrackingState.report([post.user_id]).should be_empty + TopicTrackingState.report([post.user_id]).should be_empty # lets not return anything if we scope on non-existing topic - UserTrackingState.report([user.id], post.topic_id + 1).should be_empty + TopicTrackingState.report([user.id], post.topic_id + 1).should be_empty # when we reply the poster should have an unread row Fabricate(:post, user: user, topic: post.topic) - report = UserTrackingState.report([post.user_id, user.id]) + report = TopicTrackingState.report([post.user_id, user.id]) report.length.should == 1 row = report[0] @@ -51,6 +56,6 @@ post.topic.category_id = category.id post.topic.save - UserTrackingState.report([post.user_id, user.id]).count.should == 0 + TopicTrackingState.report([post.user_id, user.id]).count.should == 0 end end diff --git a/spec/models/topic_user_spec.rb b/spec/models/topic_user_spec.rb index 0a67ea6d3efb8..927d45b0593a6 100644 --- a/spec/models/topic_user_spec.rb +++ b/spec/models/topic_user_spec.rb @@ -216,6 +216,14 @@ end + it "can scope by tracking" do + TopicUser.create!(user_id: 1, topic_id: 1, notification_level: TopicUser.notification_levels[:tracking]) + TopicUser.create!(user_id: 2, topic_id: 1, notification_level: TopicUser.notification_levels[:watching]) + TopicUser.create!(user_id: 3, topic_id: 1, notification_level: TopicUser.notification_levels[:regular]) + + TopicUser.tracking(1).count.should == 2 + TopicUser.tracking(10).count.should == 0 + end it "is able to self heal" do p1 = Fabricate(:post)