mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
This commit introduces a per-stream topic_history class
inside of topic_data.js to better encapsulate how we store topic
history.
To the callers, nothing changes here. (Some of our non-black-box
node tests change their way of setting up data, though, since the
internal data structures are different.)
The new class has the following improvements:
* We use message_id instead of timestamp as our sorting key.
(We could have done this in a prep commit, but it wouldn't
have made the diff much cleaner here.)
* We use a dictionary instead of a sorted list to store the
data, so that writes are O(1) instead of O(NlogN). Reads
now do sorts, so they're O(NlogN) instead of O(N), but reads
are fairly infrequent. (The main goal here isn't actually
performance, but instead it just simplifies the
implementation.)
* We isolate `topic_history` from the format of the messages.
This prepares us for upcoming changes where updates to the
data structure may come from topic history queries as well
as messages.
* We split out the message-add path from the message-remove
path. This prepares us to eventually get rid of the "count"
mechanism that is kind of fragile and which has to be
bypassed for historical topics.
468 lines
14 KiB
JavaScript
468 lines
14 KiB
JavaScript
set_global('page_params', {
|
|
is_admin: false,
|
|
realm_users: [],
|
|
});
|
|
|
|
set_global('$', function () {
|
|
});
|
|
|
|
add_dependencies({
|
|
marked: 'third/marked/lib/marked.js',
|
|
people: 'js/people.js',
|
|
stream_color: 'js/stream_color.js',
|
|
narrow: 'js/narrow.js',
|
|
hash_util: 'js/hash_util.js',
|
|
hashchange: 'js/hashchange.js',
|
|
topic_data: 'js/topic_data.js',
|
|
util: 'js/util.js',
|
|
});
|
|
|
|
set_global('blueslip', {});
|
|
|
|
var stream_data = require('js/stream_data.js');
|
|
|
|
var people = global.people;
|
|
|
|
(function test_basics() {
|
|
var denmark = {
|
|
subscribed: false,
|
|
color: 'blue',
|
|
name: 'Denmark',
|
|
stream_id: 1,
|
|
in_home_view: false,
|
|
};
|
|
var social = {
|
|
subscribed: true,
|
|
color: 'red',
|
|
name: 'social',
|
|
stream_id: 2,
|
|
in_home_view: true,
|
|
invite_only: true,
|
|
};
|
|
var test = {
|
|
subscribed: true,
|
|
color: 'yellow',
|
|
name: 'test',
|
|
stream_id: 3,
|
|
in_home_view: false,
|
|
invite_only: false,
|
|
};
|
|
stream_data.add_sub('Denmark', denmark);
|
|
stream_data.add_sub('social', social);
|
|
assert(stream_data.all_subscribed_streams_are_in_home_view());
|
|
stream_data.add_sub('test', test);
|
|
assert(!stream_data.all_subscribed_streams_are_in_home_view());
|
|
|
|
assert.equal(stream_data.get_sub('denmark'), denmark);
|
|
assert.equal(stream_data.get_sub('Social'), social);
|
|
|
|
assert.deepEqual(stream_data.home_view_stream_names(), ['social']);
|
|
assert.deepEqual(stream_data.subscribed_streams(), ['social', 'test']);
|
|
assert.deepEqual(stream_data.get_colors(), ['red', 'yellow']);
|
|
|
|
assert(stream_data.is_subscribed('social'));
|
|
assert(stream_data.is_subscribed('Social'));
|
|
assert(!stream_data.is_subscribed('Denmark'));
|
|
assert(!stream_data.is_subscribed('Rome'));
|
|
|
|
assert(stream_data.get_invite_only('social'));
|
|
assert(!stream_data.get_invite_only('unknown'));
|
|
assert.equal(stream_data.get_color('social'), 'red');
|
|
assert.equal(stream_data.get_color('unknown'), global.stream_color.default_color);
|
|
|
|
assert.equal(stream_data.get_name('denMARK'), 'Denmark');
|
|
assert.equal(stream_data.get_name('unknown Stream'), 'unknown Stream');
|
|
|
|
assert(stream_data.in_home_view(social.stream_id));
|
|
assert(!stream_data.in_home_view(denmark.stream_id));
|
|
}());
|
|
|
|
(function test_renames() {
|
|
stream_data.clear_subscriptions();
|
|
var id = 42;
|
|
var sub = {
|
|
name: 'Denmark',
|
|
subscribed: true,
|
|
color: 'red',
|
|
stream_id: id,
|
|
};
|
|
stream_data.add_sub('Denmark', sub);
|
|
sub = stream_data.get_sub('Denmark');
|
|
assert.equal(sub.color, 'red');
|
|
sub = stream_data.get_sub_by_id(id);
|
|
assert.equal(sub.color, 'red');
|
|
|
|
stream_data.rename_sub(sub, 'Sweden');
|
|
sub = stream_data.get_sub_by_id(id);
|
|
assert.equal(sub.color, 'red');
|
|
assert.equal(sub.name, 'Sweden');
|
|
|
|
sub = stream_data.get_sub('Denmark');
|
|
assert.equal(sub, undefined);
|
|
|
|
sub = stream_data.get_sub_by_name('Denmark');
|
|
assert.equal(sub.name, 'Sweden');
|
|
|
|
var actual_id = stream_data.get_stream_id('Denmark');
|
|
assert.equal(actual_id, 42);
|
|
}());
|
|
|
|
(function test_unsubscribe() {
|
|
stream_data.clear_subscriptions();
|
|
|
|
var sub = {name: 'devel', subscribed: false, stream_id: 1};
|
|
var me = {
|
|
email: 'me@zulip.com',
|
|
full_name: 'Current User',
|
|
user_id: 81,
|
|
};
|
|
|
|
// set up user data
|
|
people.add(me);
|
|
people.initialize_current_user(me.user_id);
|
|
|
|
// set up our subscription
|
|
stream_data.add_sub('devel', sub);
|
|
sub.subscribed = true;
|
|
stream_data.set_subscribers(sub, [me.user_id]);
|
|
|
|
// ensure our setup is accurate
|
|
assert(stream_data.is_subscribed('devel'));
|
|
|
|
// DO THE UNSUBSCRIBE HERE
|
|
stream_data.unsubscribe_myself(sub);
|
|
assert(!sub.subscribed);
|
|
assert(!stream_data.is_subscribed('devel'));
|
|
|
|
// make sure subsequent calls work
|
|
sub = stream_data.get_sub('devel');
|
|
assert(!sub.subscribed);
|
|
}());
|
|
|
|
(function test_subscribers() {
|
|
stream_data.clear_subscriptions();
|
|
var sub = {name: 'Rome', subscribed: true, stream_id: 1};
|
|
|
|
stream_data.add_sub('Rome', sub);
|
|
|
|
var fred = {
|
|
email: 'fred@zulip.com',
|
|
full_name: 'Fred',
|
|
user_id: 101,
|
|
};
|
|
var not_fred = {
|
|
email: 'not_fred@zulip.com',
|
|
full_name: 'Not Fred',
|
|
user_id: 102,
|
|
};
|
|
var george = {
|
|
email: 'george@zulip.com',
|
|
full_name: 'George',
|
|
user_id: 103,
|
|
};
|
|
people.add(fred);
|
|
people.add(not_fred);
|
|
people.add(george);
|
|
|
|
stream_data.set_subscribers(sub, [fred.user_id, george.user_id]);
|
|
assert(stream_data.user_is_subscribed('Rome', 'FRED@zulip.com'));
|
|
assert(stream_data.user_is_subscribed('Rome', 'fred@zulip.com'));
|
|
assert(stream_data.user_is_subscribed('Rome', 'george@zulip.com'));
|
|
assert(!stream_data.user_is_subscribed('Rome', 'not_fred@zulip.com'));
|
|
|
|
stream_data.set_subscribers(sub, []);
|
|
|
|
var email = 'brutus@zulip.com';
|
|
var brutus = {
|
|
email: email,
|
|
full_name: 'Brutus',
|
|
user_id: 104,
|
|
};
|
|
people.add(brutus);
|
|
assert(!stream_data.user_is_subscribed('Rome', email));
|
|
|
|
// add
|
|
var ok = stream_data.add_subscriber('Rome', brutus.user_id);
|
|
assert(ok);
|
|
assert(stream_data.user_is_subscribed('Rome', email));
|
|
sub = stream_data.get_sub('Rome');
|
|
stream_data.update_subscribers_count(sub);
|
|
assert.equal(sub.subscriber_count, 1);
|
|
|
|
// verify that adding an already-added subscriber is a noop
|
|
stream_data.add_subscriber('Rome', brutus.user_id);
|
|
assert(stream_data.user_is_subscribed('Rome', email));
|
|
sub = stream_data.get_sub('Rome');
|
|
stream_data.update_subscribers_count(sub);
|
|
assert.equal(sub.subscriber_count, 1);
|
|
|
|
// remove
|
|
ok = stream_data.remove_subscriber('Rome', brutus.user_id);
|
|
assert(ok);
|
|
assert(!stream_data.user_is_subscribed('Rome', email));
|
|
sub = stream_data.get_sub('Rome');
|
|
stream_data.update_subscribers_count(sub);
|
|
assert.equal(sub.subscriber_count, 0);
|
|
|
|
// Defensive code will give warnings, which we ignore for the
|
|
// tests, but the defensive code needs to not actually blow up.
|
|
global.blueslip.warn = function () {};
|
|
|
|
// verify that removing an already-removed subscriber is a noop
|
|
ok = stream_data.remove_subscriber('Rome', brutus.user_id);
|
|
assert(!ok);
|
|
assert(!stream_data.user_is_subscribed('Rome', email));
|
|
sub = stream_data.get_sub('Rome');
|
|
stream_data.update_subscribers_count(sub);
|
|
assert.equal(sub.subscriber_count, 0);
|
|
|
|
// Verify defensive code in set_subscribers, where the second parameter
|
|
// can be undefined.
|
|
stream_data.set_subscribers(sub);
|
|
stream_data.add_sub('Rome', sub);
|
|
stream_data.add_subscriber('Rome', brutus.user_id);
|
|
sub.subscribed = true;
|
|
assert(stream_data.user_is_subscribed('Rome', email));
|
|
|
|
// Verify that we noop and don't crash when unsubscribed.
|
|
sub.subscribed = false;
|
|
ok = stream_data.add_subscriber('Rome', brutus.user_id);
|
|
assert(ok);
|
|
assert.equal(stream_data.user_is_subscribed('Rome', email), undefined);
|
|
stream_data.remove_subscriber('Rome', brutus.user_id);
|
|
assert.equal(stream_data.user_is_subscribed('Rome', email), undefined);
|
|
|
|
// Verify that we don't crash and return false for a bad stream.
|
|
ok = stream_data.add_subscriber('UNKNOWN', brutus.user_id);
|
|
assert(!ok);
|
|
|
|
// Verify that we don't crash and return false for a bad user id.
|
|
global.blueslip.error = function () {};
|
|
ok = stream_data.add_subscriber('Rome', 9999999);
|
|
assert(!ok);
|
|
}());
|
|
|
|
(function test_process_message() {
|
|
var stream_id = 55;
|
|
|
|
var rome = {
|
|
name: 'Rome',
|
|
stream_id: stream_id,
|
|
};
|
|
|
|
stream_data.add_sub('Rome', rome);
|
|
|
|
var message1 = {
|
|
stream_id: stream_id,
|
|
id: 101,
|
|
subject: 'toPic1',
|
|
};
|
|
topic_data.process_message(message1);
|
|
|
|
var history = topic_data.get_recent_names(rome.stream_id);
|
|
assert.deepEqual(history, ['toPic1']);
|
|
|
|
var message2 = {
|
|
stream_id: stream_id,
|
|
id: 102,
|
|
subject: 'Topic1',
|
|
};
|
|
topic_data.process_message(message2);
|
|
history = topic_data.get_recent_names(rome.stream_id);
|
|
assert.deepEqual(history, ['Topic1']);
|
|
|
|
var message3 = {
|
|
stream_id: stream_id,
|
|
id: 103,
|
|
subject: 'topic2',
|
|
};
|
|
topic_data.process_message(message3);
|
|
history = topic_data.get_recent_names(rome.stream_id);
|
|
assert.deepEqual(history, ['topic2', 'Topic1']);
|
|
|
|
// Removing first topic1 message has no effect.
|
|
topic_data.process_message(message1, true);
|
|
history = topic_data.get_recent_names(rome.stream_id);
|
|
assert.deepEqual(history, ['topic2', 'Topic1']);
|
|
|
|
// Remove second topic1 message removes the topic.
|
|
topic_data.process_message(message2, true);
|
|
history = topic_data.get_recent_names(rome.stream_id);
|
|
assert.deepEqual(history, ['topic2']);
|
|
|
|
// Test that duplicate remove does not crash us.
|
|
topic_data.process_message(message2, true);
|
|
history = topic_data.get_recent_names(rome.stream_id);
|
|
assert.deepEqual(history, ['topic2']);
|
|
|
|
// get to 100% coverage for defensive code
|
|
topic_data.remove_message({
|
|
stream_id: 9999999,
|
|
});
|
|
}());
|
|
|
|
(function test_is_active() {
|
|
stream_data.clear_subscriptions();
|
|
|
|
var sub = {name: 'pets', subscribed: false, stream_id: 1};
|
|
stream_data.add_sub('pets', sub);
|
|
|
|
assert(!stream_data.is_active(sub));
|
|
|
|
stream_data.subscribe_myself(sub);
|
|
assert(stream_data.is_active(sub));
|
|
|
|
stream_data.unsubscribe_myself(sub);
|
|
assert(!stream_data.is_active(sub));
|
|
|
|
sub = {name: 'lunch', subscribed: false, stream_id: 222};
|
|
stream_data.add_sub('lunch', sub);
|
|
|
|
assert(!stream_data.is_active(sub));
|
|
|
|
var message = {
|
|
stream_id: 222,
|
|
id: 108,
|
|
subject: 'topic2',
|
|
};
|
|
topic_data.process_message(message);
|
|
|
|
assert(stream_data.is_active(sub));
|
|
}());
|
|
|
|
(function test_admin_options() {
|
|
function make_sub() {
|
|
var sub = {
|
|
subscribed: false,
|
|
color: 'blue',
|
|
name: 'stream_to_admin',
|
|
stream_id: 1,
|
|
in_home_view: false,
|
|
invite_only: false,
|
|
};
|
|
stream_data.add_sub(sub.name, sub);
|
|
return sub;
|
|
}
|
|
|
|
// non-admins can't do anything
|
|
global.page_params.is_admin = false;
|
|
var sub = make_sub();
|
|
stream_data.update_calculated_fields(sub);
|
|
assert(!sub.is_admin);
|
|
assert(!sub.can_make_public);
|
|
assert(!sub.can_make_private);
|
|
|
|
// just a sanity check that we leave "normal" fields alone
|
|
assert.equal(sub.color, 'blue');
|
|
|
|
// the remaining cases are for admin users
|
|
global.page_params.is_admin = true;
|
|
|
|
// admins can make public streams become private
|
|
sub = make_sub();
|
|
stream_data.update_calculated_fields(sub);
|
|
assert(sub.is_admin);
|
|
assert(!sub.can_make_public);
|
|
assert(sub.can_make_private);
|
|
|
|
// admins can only make private streams become public
|
|
// if they are subscribed
|
|
sub = make_sub();
|
|
sub.invite_only = true;
|
|
sub.subscribed = false;
|
|
stream_data.update_calculated_fields(sub);
|
|
assert(sub.is_admin);
|
|
assert(!sub.can_make_public);
|
|
assert(!sub.can_make_private);
|
|
|
|
sub = make_sub();
|
|
sub.invite_only = true;
|
|
sub.subscribed = true;
|
|
stream_data.update_calculated_fields(sub);
|
|
assert(sub.is_admin);
|
|
assert(sub.can_make_public);
|
|
assert(!sub.can_make_private);
|
|
}());
|
|
|
|
(function test_stream_settings() {
|
|
var cinnamon = {
|
|
stream_id: 1,
|
|
name: 'c',
|
|
color: 'cinnamon',
|
|
subscribed: true,
|
|
};
|
|
|
|
var blue = {
|
|
stream_id: 2,
|
|
name: 'b',
|
|
color: 'blue',
|
|
subscribed: false,
|
|
};
|
|
|
|
var amber = {
|
|
stream_id: 3,
|
|
name: 'a',
|
|
color: 'amber',
|
|
subscribed: true,
|
|
};
|
|
stream_data.clear_subscriptions();
|
|
stream_data.add_sub(cinnamon.name, cinnamon);
|
|
stream_data.add_sub(amber.name, amber);
|
|
stream_data.add_sub(blue.name, blue);
|
|
|
|
var sub_rows = stream_data.get_streams_for_settings_page();
|
|
assert.equal(sub_rows[0].color, 'blue');
|
|
assert.equal(sub_rows[1].color, 'amber');
|
|
assert.equal(sub_rows[2].color, 'cinnamon');
|
|
|
|
}());
|
|
|
|
(function test_delete_sub() {
|
|
var canada = {
|
|
stream_id: 101,
|
|
name: 'Canada',
|
|
subscribed: true,
|
|
};
|
|
|
|
stream_data.clear_subscriptions();
|
|
stream_data.add_sub('Canada', canada);
|
|
|
|
assert(stream_data.is_subscribed('Canada'));
|
|
assert(stream_data.get_sub('Canada').stream_id, canada.stream_id);
|
|
assert(stream_data.get_sub_by_id(canada.stream_id).name, 'Canada');
|
|
|
|
stream_data.delete_sub(canada.stream_id);
|
|
assert(!stream_data.is_subscribed('Canada'));
|
|
assert(!stream_data.get_sub('Canada'));
|
|
assert(!stream_data.get_sub_by_id(canada.stream_id));
|
|
}());
|
|
|
|
(function test_get_subscriber_count() {
|
|
var india = {
|
|
stream_id: 102,
|
|
name: 'India',
|
|
subscribed: true,
|
|
};
|
|
stream_data.clear_subscriptions();
|
|
assert.equal(stream_data.get_subscriber_count('India'), undefined);
|
|
stream_data.add_sub('India', india);
|
|
assert.equal(stream_data.get_subscriber_count('India'), 0);
|
|
|
|
var fred = {
|
|
email: 'fred@zulip.com',
|
|
full_name: 'Fred',
|
|
user_id: 101,
|
|
};
|
|
people.add(fred);
|
|
stream_data.add_subscriber('India', 102);
|
|
assert.equal(stream_data.get_subscriber_count('India'), 1);
|
|
var george = {
|
|
email: 'george@zulip.com',
|
|
full_name: 'George',
|
|
user_id: 103,
|
|
};
|
|
people.add(george);
|
|
stream_data.add_subscriber('India', 103);
|
|
assert.equal(stream_data.get_subscriber_count('India'), 2);
|
|
}());
|