mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
Add discourse-presence as a core plugin (#5137)
* Add discourse-presence as a core plugin * Default enabled
This commit is contained in:
parent
4d840d10db
commit
c9912fcc37
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -54,6 +54,7 @@ bootsnap-compile-cache/
|
||||||
!/plugins/discourse-details/
|
!/plugins/discourse-details/
|
||||||
!/plugins/discourse-nginx-performance-report
|
!/plugins/discourse-nginx-performance-report
|
||||||
!/plugins/discourse-narrative-bot
|
!/plugins/discourse-narrative-bot
|
||||||
|
!/plugins/discourse-presence
|
||||||
/plugins/*/auto_generated/
|
/plugins/*/auto_generated/
|
||||||
|
|
||||||
/spec/fixtures/plugins/my_plugin/auto_generated
|
/spec/fixtures/plugins/my_plugin/auto_generated
|
||||||
|
|
14
plugins/discourse-presence/README.md
Normal file
14
plugins/discourse-presence/README.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Discourse Presence plugin
|
||||||
|
This plugin shows which users are currently writing a reply at the same time as you.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Follow the directions at [Install a Plugin](https://meta.discourse.org/t/install-a-plugin/19157) using https://github.com/discourse/discourse-presence.git as the repository URL.
|
||||||
|
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
André Pereira, David Taylor
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GNU GPL v2
|
|
@ -0,0 +1,21 @@
|
||||||
|
import computed from 'ember-addons/ember-computed-decorators';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
composer: Ember.inject.controller(),
|
||||||
|
|
||||||
|
@computed('composer.presenceUsers', 'currentUser.id')
|
||||||
|
users(presenceUsers, currentUser_id){
|
||||||
|
return presenceUsers.filter(user => user.id !== currentUser_id);
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('composer.presenceState.action')
|
||||||
|
isReply(action){
|
||||||
|
return action === 'reply';
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('users.length')
|
||||||
|
shouldDisplay(length){
|
||||||
|
return length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { ajax } from 'discourse/lib/ajax';
|
||||||
|
import { observes} from 'ember-addons/ember-computed-decorators';
|
||||||
|
import { withPluginApi } from 'discourse/lib/plugin-api';
|
||||||
|
import pageVisible from 'discourse/lib/page-visible';
|
||||||
|
|
||||||
|
function initialize(api) {
|
||||||
|
api.modifyClass('controller:composer', {
|
||||||
|
|
||||||
|
oldPresenceState: { compose_state: 'closed' },
|
||||||
|
presenceState: { compose_state: 'closed' },
|
||||||
|
keepAliveTimer: null,
|
||||||
|
messageBusChannel: null,
|
||||||
|
|
||||||
|
@observes('model.composeState', 'model.action', 'model.post', 'model.topic')
|
||||||
|
openStatusChanged(){
|
||||||
|
Ember.run.once(this, 'updateStateObject');
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStateObject(){
|
||||||
|
const composeState = this.get('model.composeState');
|
||||||
|
|
||||||
|
const stateObject = {
|
||||||
|
compose_state: composeState ? composeState : 'closed'
|
||||||
|
};
|
||||||
|
|
||||||
|
if(stateObject.compose_state === 'open'){
|
||||||
|
stateObject.action = this.get('model.action');
|
||||||
|
|
||||||
|
// Add some context if we're editing or replying
|
||||||
|
switch(stateObject.action){
|
||||||
|
case 'edit':
|
||||||
|
stateObject.post_id = this.get('model.post.id');
|
||||||
|
break;
|
||||||
|
case 'reply':
|
||||||
|
stateObject.topic_id = this.get('model.topic.id');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break; // createTopic or privateMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('oldPresenceState', this.get('presenceState'));
|
||||||
|
this.set('presenceState', stateObject);
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldSharePresence(){
|
||||||
|
const isOpen = this.get('presenceState.compose_state') !== 'open';
|
||||||
|
const isEditing = ['edit','reply'].includes(this.get('presenceState.action'));
|
||||||
|
return isOpen && isEditing;
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes('presenceState')
|
||||||
|
presenceStateChanged(){
|
||||||
|
if(this.get('messageBusChannel')){
|
||||||
|
this.messageBus.unsubscribe(this.get('messageBusChannel'));
|
||||||
|
this.set('messageBusChannel', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('presenceUsers', []);
|
||||||
|
|
||||||
|
ajax('/presence/publish/', {
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
response_needed: true,
|
||||||
|
previous: this.get('oldPresenceState'),
|
||||||
|
current: this.get('presenceState')
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
const messageBusChannel = data['messagebus_channel'];
|
||||||
|
if(messageBusChannel){
|
||||||
|
const users = data['users'];
|
||||||
|
const messageBusId = data['messagebus_id'];
|
||||||
|
this.set('presenceUsers', users);
|
||||||
|
this.set('messageBusChannel', messageBusChannel);
|
||||||
|
this.messageBus.subscribe(messageBusChannel, message => {
|
||||||
|
this.set('presenceUsers', message['users']);
|
||||||
|
}, messageBusId);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
// This isn't a critical failure, so don't disturb the user
|
||||||
|
console.error("Error publishing composer status", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Ember.run.cancel(this.get('keepAliveTimer'));
|
||||||
|
if(this.shouldSharePresence()){
|
||||||
|
// Send presence data every 10 seconds
|
||||||
|
this.set('keepAliveTimer', Ember.run.later(this, 'keepPresenceAlive', 10000));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
keepPresenceAlive(){
|
||||||
|
// If the composer isn't open, or we're not editing,
|
||||||
|
// don't update anything, and don't schedule this task again
|
||||||
|
if(!this.shouldSharePresence()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only send the keepalive message if the browser has focus
|
||||||
|
if(pageVisible()){
|
||||||
|
ajax('/presence/publish/', {
|
||||||
|
type: 'POST',
|
||||||
|
data: { current: this.get('presenceState') }
|
||||||
|
}).catch((error) => {
|
||||||
|
// This isn't a critical failure, so don't disturb the user
|
||||||
|
console.error("Error publishing composer status", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule again in another 30 seconds
|
||||||
|
Ember.run.cancel(this.get('keepAliveTimer'));
|
||||||
|
this.set('keepAliveTimer', Ember.run.later(this, 'keepPresenceAlive', 10000));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "composer-controller-presence",
|
||||||
|
after: "message-bus",
|
||||||
|
|
||||||
|
initialize(container) {
|
||||||
|
const siteSettings = container.lookup('site-settings:main');
|
||||||
|
if (siteSettings.presence_enabled) withPluginApi('0.8.9', initialize);
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,18 @@
|
||||||
|
{{#if shouldDisplay}}
|
||||||
|
<div class="presence-users">
|
||||||
|
{{#each users as |user|}}
|
||||||
|
{{avatar user avatarTemplatePath="avatar_template" usernamePath="username" imageSize="small"}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<span class="presence_text">
|
||||||
|
<span class="description">
|
||||||
|
{{#if isReply ~}}
|
||||||
|
{{i18n 'presence.is_replying' count=users.length}}
|
||||||
|
{{~else~}}
|
||||||
|
{{i18n 'presence.is_editing' count=users.length}}
|
||||||
|
{{~/if}}</span>{{!-- (using comment to stop whitespace)
|
||||||
|
--}}</span>{{!--
|
||||||
|
--}}<span class="wave"><span class="dot">.</span><span class="dot">.</span><span class="dot">.</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{{#if siteSettings.presence_enabled}}
|
||||||
|
{{composer-presence-display}}
|
||||||
|
{{/if}}
|
45
plugins/discourse-presence/assets/stylesheets/presence.scss
Normal file
45
plugins/discourse-presence/assets/stylesheets/presence.scss
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
.presence-users{
|
||||||
|
|
||||||
|
background-color: $primary-low;
|
||||||
|
|
||||||
|
color: $primary-medium;
|
||||||
|
padding: 0px 5px;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 30px;
|
||||||
|
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
display: inline-block;
|
||||||
|
animation: wave 1.8s linear infinite;
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation-delay: -1.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation-delay: -1.4s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wave {
|
||||||
|
0%, 60%, 100% {
|
||||||
|
transform: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
transform: translateY(-0.2em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-view .presence-users{
|
||||||
|
top: 5px;
|
||||||
|
right: 60px;
|
||||||
|
.description{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
}
|
9
plugins/discourse-presence/config/locales/client.en.yml
Normal file
9
plugins/discourse-presence/config/locales/client.en.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
en:
|
||||||
|
js:
|
||||||
|
presence:
|
||||||
|
is_replying:
|
||||||
|
one: "is also replying"
|
||||||
|
other: "are also replying"
|
||||||
|
is_editing:
|
||||||
|
one: "is also editing"
|
||||||
|
other: "are also editing"
|
3
plugins/discourse-presence/config/locales/server.en.yml
Normal file
3
plugins/discourse-presence/config/locales/server.en.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
en:
|
||||||
|
site_settings:
|
||||||
|
presence_enabled: 'Show users that are currently replying to the current topic, or editing the current post?'
|
4
plugins/discourse-presence/config/settings.yml
Normal file
4
plugins/discourse-presence/config/settings.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
plugins:
|
||||||
|
presence_enabled:
|
||||||
|
default: true
|
||||||
|
client: true
|
148
plugins/discourse-presence/plugin.rb
Normal file
148
plugins/discourse-presence/plugin.rb
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
# name: discourse-presence
|
||||||
|
# about: Show which users are writing a reply to a topic
|
||||||
|
# version: 1.0
|
||||||
|
# authors: André Pereira, David Taylor
|
||||||
|
# url: https://github.com/discourse/discourse-presence.git
|
||||||
|
|
||||||
|
enabled_site_setting :presence_enabled
|
||||||
|
|
||||||
|
register_asset 'stylesheets/presence.scss'
|
||||||
|
|
||||||
|
PLUGIN_NAME ||= "discourse-presence".freeze
|
||||||
|
|
||||||
|
after_initialize do
|
||||||
|
|
||||||
|
module ::Presence
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
engine_name PLUGIN_NAME
|
||||||
|
isolate_namespace Presence
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ::Presence::PresenceManager
|
||||||
|
def self.get_redis_key(type, id)
|
||||||
|
"presence:#{type}:#{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.get_messagebus_channel(type, id)
|
||||||
|
"/presence/#{type}/#{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.add(type, id, user_id)
|
||||||
|
redis_key = get_redis_key(type, id)
|
||||||
|
response = $redis.hset(redis_key, user_id, Time.zone.now)
|
||||||
|
|
||||||
|
response # Will be true if a new key
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.remove(type, id, user_id)
|
||||||
|
redis_key = get_redis_key(type, id)
|
||||||
|
response = $redis.hdel(redis_key, user_id)
|
||||||
|
|
||||||
|
response > 0 # Return true if key was actually deleted
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.get_users(type, id)
|
||||||
|
redis_key = get_redis_key(type, id)
|
||||||
|
user_ids = $redis.hkeys(redis_key).map(&:to_i)
|
||||||
|
|
||||||
|
User.where(id: user_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.publish(type, id)
|
||||||
|
users = get_users(type, id)
|
||||||
|
serialized_users = users.map { |u| BasicUserSerializer.new(u, root: false) }
|
||||||
|
message = {
|
||||||
|
users: serialized_users
|
||||||
|
}
|
||||||
|
MessageBus.publish(get_messagebus_channel(type, id), message.as_json)
|
||||||
|
|
||||||
|
users
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.cleanup(type, id)
|
||||||
|
hash = $redis.hgetall(get_redis_key(type, id))
|
||||||
|
original_hash_size = hash.length
|
||||||
|
|
||||||
|
any_changes = false
|
||||||
|
|
||||||
|
# Delete entries older than 20 seconds
|
||||||
|
hash.each do |user_id, time|
|
||||||
|
if Time.zone.now - Time.parse(time) >= 20
|
||||||
|
any_changes ||= remove(type, id, user_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
any_changes
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
require_dependency "application_controller"
|
||||||
|
|
||||||
|
class Presence::PresencesController < ::ApplicationController
|
||||||
|
before_filter :ensure_logged_in
|
||||||
|
|
||||||
|
def publish
|
||||||
|
data = params.permit(:response_needed,
|
||||||
|
current: [:compose_state, :action, :topic_id, :post_id],
|
||||||
|
previous: [:compose_state, :action, :topic_id, :post_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
if data[:previous] &&
|
||||||
|
data[:previous][:compose_state] == 'open' &&
|
||||||
|
data[:previous][:action].in?(['edit', 'reply'])
|
||||||
|
|
||||||
|
type = data[:previous][:post_id] ? 'post' : 'topic'
|
||||||
|
id = data[:previous][:post_id] ? data[:previous][:post_id] : data[:previous][:topic_id]
|
||||||
|
|
||||||
|
any_changes = false
|
||||||
|
any_changes ||= Presence::PresenceManager.remove(type, id, current_user.id)
|
||||||
|
any_changes ||= Presence::PresenceManager.cleanup(type, id)
|
||||||
|
|
||||||
|
users = Presence::PresenceManager.publish(type, id) if any_changes
|
||||||
|
end
|
||||||
|
|
||||||
|
if data[:current] &&
|
||||||
|
data[:current][:compose_state] == 'open' &&
|
||||||
|
data[:current][:action].in?(['edit', 'reply'])
|
||||||
|
|
||||||
|
type = data[:current][:post_id] ? 'post' : 'topic'
|
||||||
|
id = data[:current][:post_id] ? data[:current][:post_id] : data[:current][:topic_id]
|
||||||
|
|
||||||
|
any_changes = false
|
||||||
|
any_changes ||= Presence::PresenceManager.add(type, id, current_user.id)
|
||||||
|
any_changes ||= Presence::PresenceManager.cleanup(type, id)
|
||||||
|
|
||||||
|
users = Presence::PresenceManager.publish(type, id) if any_changes
|
||||||
|
|
||||||
|
if data[:response_needed]
|
||||||
|
users ||= Presence::PresenceManager.get_users(type, id)
|
||||||
|
|
||||||
|
serialized_users = users.map { |u| BasicUserSerializer.new(u, root: false) }
|
||||||
|
|
||||||
|
messagebus_channel = Presence::PresenceManager.get_messagebus_channel(type, id)
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
messagebus_channel: messagebus_channel,
|
||||||
|
messagebus_id: MessageBus.last_id(messagebus_channel),
|
||||||
|
users: serialized_users
|
||||||
|
}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
Presence::Engine.routes.draw do
|
||||||
|
post '/publish' => 'presences#publish'
|
||||||
|
end
|
||||||
|
|
||||||
|
Discourse::Application.routes.append do
|
||||||
|
mount ::Presence::Engine, at: '/presence'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
80
plugins/discourse-presence/spec/presence_controller_spec.rb
Normal file
80
plugins/discourse-presence/spec/presence_controller_spec.rb
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ::Presence::PresencesController, type: :request do
|
||||||
|
|
||||||
|
before do
|
||||||
|
SiteSetting.presence_enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:user1) { Fabricate(:user) }
|
||||||
|
let(:user2) { Fabricate(:user) }
|
||||||
|
let(:user3) { Fabricate(:user) }
|
||||||
|
|
||||||
|
after(:each) do
|
||||||
|
$redis.del('presence:post:22')
|
||||||
|
$redis.del('presence:post:11')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not logged in' do
|
||||||
|
it 'should raise the right error' do
|
||||||
|
expect { post '/presence/publish.json' }.to raise_error(Discourse::NotLoggedIn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when logged in' do
|
||||||
|
before do
|
||||||
|
sign_in(user1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't produce an error" do
|
||||||
|
expect { post '/presence/publish.json' }.not_to raise_error
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a response when requested" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
post '/presence/publish.json', current: { compose_state: 'open', action: 'edit', post_id: 22 }, response_needed: true
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to eq (1)
|
||||||
|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
|
||||||
|
expect(data['messagebus_channel']).to eq('/presence/post/22')
|
||||||
|
expect(data['messagebus_id']).to eq(MessageBus.last_id('/presence/post/22'))
|
||||||
|
expect(data['users'][0]["id"]).to eq(user1.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't return a response when not requested" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
post '/presence/publish.json', current: { compose_state: 'open', action: 'edit', post_id: 22 }
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to eq (1)
|
||||||
|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data).to eq({})
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send duplicate messagebus messages" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
post '/presence/publish.json', current: { compose_state: 'open', action: 'edit', post_id: 22 }
|
||||||
|
end
|
||||||
|
expect(messages.count).to eq (1)
|
||||||
|
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
post '/presence/publish.json', current: { compose_state: 'open', action: 'edit', post_id: 22 }
|
||||||
|
end
|
||||||
|
expect(messages.count).to eq (0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "clears 'previous' state when supplied" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
post '/presence/publish.json', current: { compose_state: 'open', action: 'edit', post_id: 22 }
|
||||||
|
post '/presence/publish.json', current: { compose_state: 'open', action: 'edit', post_id: 11 }, previous: { compose_state: 'open', action: 'edit', post_id: 22 }
|
||||||
|
end
|
||||||
|
expect(messages.count).to eq (3)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
64
plugins/discourse-presence/spec/presence_manager_spec.rb
Normal file
64
plugins/discourse-presence/spec/presence_manager_spec.rb
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ::Presence::PresenceManager do
|
||||||
|
|
||||||
|
let(:user1) { Fabricate(:user) }
|
||||||
|
let(:user2) { Fabricate(:user) }
|
||||||
|
let(:user3) { Fabricate(:user) }
|
||||||
|
let(:manager) { ::Presence::PresenceManager }
|
||||||
|
|
||||||
|
after(:each) do
|
||||||
|
$redis.del('presence:post:22')
|
||||||
|
$redis.del('presence:post:11')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds, removes and lists users correctly' do
|
||||||
|
expect(manager.get_users('post', 22).count).to eq(0)
|
||||||
|
|
||||||
|
expect(manager.add('post', 22, user1.id)).to be true
|
||||||
|
expect(manager.add('post', 22, user2.id)).to be true
|
||||||
|
expect(manager.add('post', 11, user3.id)).to be true
|
||||||
|
|
||||||
|
expect(manager.get_users('post', 22).count).to eq(2)
|
||||||
|
expect(manager.get_users('post', 11).count).to eq(1)
|
||||||
|
|
||||||
|
expect(manager.get_users('post', 22)).to contain_exactly(user1, user2)
|
||||||
|
expect(manager.get_users('post', 11)).to contain_exactly(user3)
|
||||||
|
|
||||||
|
expect(manager.remove('post', 22, user1.id)).to be true
|
||||||
|
expect(manager.get_users('post', 22).count).to eq(1)
|
||||||
|
expect(manager.get_users('post', 22)).to contain_exactly(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'publishes correctly' do
|
||||||
|
expect(manager.get_users('post', 22).count).to eq(0)
|
||||||
|
|
||||||
|
manager.add('post', 22, user1.id)
|
||||||
|
manager.add('post', 22, user2.id)
|
||||||
|
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
manager.publish('post', 22)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages.count).to eq (1)
|
||||||
|
message = messages.first
|
||||||
|
|
||||||
|
expect(message.channel).to eq('/presence/post/22')
|
||||||
|
|
||||||
|
expect(message.data["users"].map { |u| u[:id] }).to contain_exactly(user1.id, user2.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'cleans up correctly' do
|
||||||
|
freeze_time Time.zone.now do
|
||||||
|
expect(manager.add('post', 22, user1.id)).to be true
|
||||||
|
expect(manager.cleanup('post', 22)).to be false # Nothing to cleanup
|
||||||
|
expect(manager.get_users('post', 22).count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Anything older than 20 seconds should be cleaned up
|
||||||
|
freeze_time 30.seconds.from_now do
|
||||||
|
expect(manager.cleanup('post', 22)).to be true
|
||||||
|
expect(manager.get_users('post', 22).count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user