mirror of
https://github.com/discourse/discourse.git
synced 2024-11-28 16:56:19 +08:00
40f86829f7
* We now use a new custom view, {{cloaked-collection}} to display posts in a topic. * Posts are removed and inserted (cloaked/uncloaked) into the DOM dynamically based on whether they are visible in the current browser viewport. * There's been a lot of refactoring to ensure the relationship between the post views and the topic controller is sane. * Lots of fixes involving jumping to a post, including a new LockOn component to that tries to stay focused on an element even if stuff is loading before it in the DOM that would normally push it down.
419 lines
16 KiB
JavaScript
419 lines
16 KiB
JavaScript
module("Discourse.PostStream");
|
|
|
|
var buildStream = function(id, stream) {
|
|
var topic = Discourse.Topic.create({id: id});
|
|
var ps = topic.get('postStream');
|
|
if (stream) {
|
|
ps.set('stream', stream);
|
|
}
|
|
return ps;
|
|
};
|
|
|
|
var participant = {username: 'eviltrout'};
|
|
|
|
test('create', function() {
|
|
ok(Discourse.PostStream.create(), 'it can be created with no parameters');
|
|
});
|
|
|
|
test('defaults', function() {
|
|
var postStream = buildStream(1234);
|
|
blank(postStream.get('posts'), "there are no posts in a stream by default");
|
|
ok(!postStream.get('loaded'), "it has never loaded");
|
|
present(postStream.get('topic'));
|
|
|
|
});
|
|
|
|
test('appending posts', function() {
|
|
var postStream = buildStream(4567, [1, 3, 4]);
|
|
|
|
equal(postStream.get('lastPostId'), 4, "the last post id is 4");
|
|
|
|
ok(!postStream.get('hasPosts'), "there are no posts by default");
|
|
ok(!postStream.get('firstPostPresent'), "the first post is not loaded");
|
|
ok(!postStream.get('loadedAllPosts'), "the last post is not loaded");
|
|
equal(postStream.get('posts.length'), 0, "it has no posts initially");
|
|
|
|
postStream.appendPost(Discourse.Post.create({id: 2, post_number: 2}));
|
|
ok(!postStream.get('firstPostPresent'), "the first post is still not loaded");
|
|
equal(postStream.get('posts.length'), 1, "it has one post in the stream");
|
|
|
|
postStream.appendPost(Discourse.Post.create({id: 4, post_number: 4}));
|
|
ok(!postStream.get('firstPostPresent'), "the first post is still loaded");
|
|
ok(postStream.get('loadedAllPosts'), "the last post is now loaded");
|
|
equal(postStream.get('posts.length'), 2, "it has two posts in the stream");
|
|
|
|
postStream.appendPost(Discourse.Post.create({id: 4, post_number: 4}));
|
|
equal(postStream.get('posts.length'), 2, "it will not add the same post with id twice");
|
|
|
|
var stagedPost = Discourse.Post.create({raw: 'incomplete post'});
|
|
postStream.appendPost(stagedPost);
|
|
equal(postStream.get('posts.length'), 3, "it can handle posts without ids");
|
|
postStream.appendPost(stagedPost);
|
|
equal(postStream.get('posts.length'), 3, "it won't add the same post without an id twice");
|
|
|
|
|
|
// change the stream
|
|
postStream.set('stream', [1, 2, 4]);
|
|
ok(!postStream.get('firstPostPresent'), "the first post no longer loaded since the stream changed.");
|
|
ok(postStream.get('loadedAllPosts'), "the last post is still the last post in the new stream");
|
|
});
|
|
|
|
test('closestPostNumberFor', function() {
|
|
var postStream = buildStream(1231);
|
|
|
|
blank(postStream.closestPostNumberFor(1), "there is no closest post when nothing is loaded");
|
|
|
|
postStream.appendPost(Discourse.Post.create({id: 1, post_number: 2}));
|
|
postStream.appendPost(Discourse.Post.create({id: 2, post_number: 3}));
|
|
|
|
equal(postStream.closestPostNumberFor(2), 2, "If a post is in the stream it returns its post number");
|
|
equal(postStream.closestPostNumberFor(3), 3, "If a post is in the stream it returns its post number");
|
|
equal(postStream.closestPostNumberFor(10), 3, "it clips to the upper bound of the stream");
|
|
equal(postStream.closestPostNumberFor(0), 2, "it clips to the lower bound of the stream");
|
|
});
|
|
|
|
test('updateFromJson', function() {
|
|
var postStream = buildStream(1231);
|
|
|
|
postStream.updateFromJson({
|
|
posts: [{id: 1}],
|
|
stream: [1],
|
|
extra_property: 12
|
|
});
|
|
|
|
equal(postStream.get('posts.length'), 1, 'it loaded the posts');
|
|
containsInstance(postStream.get('posts'), Discourse.Post);
|
|
|
|
equal(postStream.get('extra_property'), 12);
|
|
});
|
|
|
|
test("removePosts", function() {
|
|
var postStream = buildStream(10000001, [1,2,3]);
|
|
|
|
var p1 = Discourse.Post.create({id: 1, post_number: 2}),
|
|
p2 = Discourse.Post.create({id: 2, post_number: 3}),
|
|
p3 = Discourse.Post.create({id: 3, post_number: 4});
|
|
|
|
postStream.appendPost(p1);
|
|
postStream.appendPost(p2);
|
|
postStream.appendPost(p3);
|
|
|
|
// Removing nothing does nothing
|
|
postStream.removePosts();
|
|
equal(postStream.get('posts.length'), 3);
|
|
|
|
postStream.removePosts([p1, p3]);
|
|
equal(postStream.get('posts.length'), 1);
|
|
deepEqual(postStream.get('stream'), [2]);
|
|
|
|
});
|
|
|
|
test("cancelFilter", function() {
|
|
var postStream = buildStream(1235);
|
|
|
|
this.stub(postStream, "refresh");
|
|
|
|
postStream.set('summary', true);
|
|
postStream.cancelFilter();
|
|
ok(!postStream.get('summary'), "summary is cancelled");
|
|
|
|
postStream.toggleParticipant(participant);
|
|
postStream.cancelFilter();
|
|
blank(postStream.get('userFilters'), "cancelling the filters clears the userFilters");
|
|
});
|
|
|
|
test("toggleParticipant", function() {
|
|
var postStream = buildStream(1236);
|
|
this.stub(postStream, "refresh");
|
|
|
|
equal(postStream.get('userFilters.length'), 0, "by default no participants are toggled");
|
|
|
|
postStream.toggleParticipant(participant.username);
|
|
ok(postStream.get('userFilters').contains('eviltrout'), 'eviltrout is in the filters');
|
|
|
|
postStream.toggleParticipant(participant.username);
|
|
blank(postStream.get('userFilters'), "toggling the participant again removes them");
|
|
});
|
|
|
|
test("streamFilters", function() {
|
|
var postStream = buildStream(1237);
|
|
this.stub(postStream, "refresh");
|
|
|
|
deepEqual(postStream.get('streamFilters'), {}, "there are no postFilters by default");
|
|
ok(postStream.get('hasNoFilters'), "there are no filters by default");
|
|
blank(postStream.get("filterDesc"), "there is no description of the filter");
|
|
|
|
postStream.set('summary', true);
|
|
deepEqual(postStream.get('streamFilters'), {filter: "summary"}, "postFilters contains the summary flag");
|
|
ok(!postStream.get('hasNoFilters'), "now there are filters present");
|
|
present(postStream.get("filterDesc"), "there is a description of the filter");
|
|
|
|
postStream.toggleParticipant(participant.username);
|
|
deepEqual(postStream.get('streamFilters'), {
|
|
filter: "summary",
|
|
username_filters: ['eviltrout']
|
|
}, "streamFilters contains the username we filtered");
|
|
});
|
|
|
|
test("loading", function() {
|
|
var postStream = buildStream(1234);
|
|
ok(!postStream.get('loading'), "we're not loading by default");
|
|
|
|
postStream.set('loadingAbove', true);
|
|
ok(postStream.get('loading'), "we're loading if loading above");
|
|
|
|
postStream = buildStream(1234);
|
|
postStream.set('loadingBelow', true);
|
|
ok(postStream.get('loading'), "we're loading if loading below");
|
|
|
|
postStream = buildStream(1234);
|
|
postStream.set('loadingFilter', true);
|
|
ok(postStream.get('loading'), "we're loading if loading a filter");
|
|
});
|
|
|
|
test("nextWindow", function() {
|
|
Discourse.SiteSettings.posts_per_page = 5;
|
|
var postStream = buildStream(1234, [1,2,3,5,8,9,10,11,13,14,15,16]);
|
|
|
|
blank(postStream.get('nextWindow'), 'With no posts loaded, the window is blank');
|
|
|
|
postStream.updateFromJson({ posts: [{id: 1}, {id: 2}] });
|
|
deepEqual(postStream.get('nextWindow'), [3,5,8,9,10],
|
|
"If we've loaded the first 2 posts, the window should be the 5 after that");
|
|
|
|
postStream.updateFromJson({ posts: [{id: 13}] });
|
|
deepEqual(postStream.get('nextWindow'), [14, 15, 16], "Boundary check: stop at the end.");
|
|
|
|
postStream.updateFromJson({ posts: [{id: 16}] });
|
|
blank(postStream.get('nextWindow'), "Once we've seen everything there's nothing to load.");
|
|
});
|
|
|
|
test("previousWindow", function() {
|
|
Discourse.SiteSettings.posts_per_page = 5;
|
|
var postStream = buildStream(1234, [1,2,3,5,8,9,10,11,13,14,15,16]);
|
|
|
|
blank(postStream.get('previousWindow'), 'With no posts loaded, the window is blank');
|
|
|
|
postStream.updateFromJson({ posts: [{id: 11}, {id: 13}] });
|
|
deepEqual(postStream.get('previousWindow'), [3, 5, 8, 9, 10],
|
|
"If we've loaded in the middle, it's the previous 5 posts");
|
|
|
|
postStream.updateFromJson({ posts: [{id: 3}] });
|
|
deepEqual(postStream.get('previousWindow'), [1, 2], "Boundary check: stop at the beginning.");
|
|
|
|
postStream.updateFromJson({ posts: [{id: 1}] });
|
|
blank(postStream.get('previousWindow'), "Once we've seen everything there's nothing to load.");
|
|
});
|
|
|
|
test("storePost", function() {
|
|
var postStream = buildStream(1234);
|
|
|
|
var post = Discourse.Post.create({id: 1, post_number: 1, raw: 'initial value'});
|
|
var stored = postStream.storePost(post);
|
|
equal(post, stored, "it returns the post it stored");
|
|
equal(post.get('topic'), postStream.get('topic'), "it creates the topic reference properly");
|
|
|
|
var dupePost = Discourse.Post.create({id: 1, post_number: 1, raw: 'updated value'});
|
|
var storedDupe = postStream.storePost(dupePost);
|
|
equal(storedDupe, post, "it returns the previously stored post instead to avoid dupes");
|
|
equal(storedDupe.get('raw'), 'updated value', 'it updates the previously stored post');
|
|
|
|
var postWithoutId = Discourse.Post.create({raw: 'hello world'});
|
|
stored = postStream.storePost(postWithoutId);
|
|
equal(stored, postWithoutId, "it returns the same post back");
|
|
equal(postStream.get('postIdentityMap.length'), 1, "it does not add a new entry into the identity map");
|
|
|
|
});
|
|
|
|
test("identity map", function() {
|
|
var postStream = buildStream(1234);
|
|
var p1 = postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1}));
|
|
var p3 = postStream.appendPost(Discourse.Post.create({id: 3, post_number: 4}));
|
|
|
|
equal(postStream.findLoadedPost(1), p1, "it can return cached posts by id");
|
|
blank(postStream.findLoadedPost(4), "it can't find uncached posts");
|
|
|
|
deepEqual(postStream.listUnloadedIds([10, 11, 12]), [10, 11, 12], "it returns a list of all unloaded ids");
|
|
blank(postStream.listUnloadedIds([1, 3]), "if we have loaded all posts it's blank");
|
|
deepEqual(postStream.listUnloadedIds([1, 2, 3, 4]), [2, 4], "it only returns unloaded posts");
|
|
});
|
|
|
|
asyncTestDiscourse("loadIntoIdentityMap with no data", function() {
|
|
var postStream = buildStream(1234);
|
|
expect(1);
|
|
|
|
this.stub(Discourse, "ajax");
|
|
postStream.loadIntoIdentityMap([]).then(function() {
|
|
ok(!Discourse.ajax.calledOnce, "an empty array returned a promise yet performed no ajax request");
|
|
start();
|
|
});
|
|
});
|
|
|
|
asyncTestDiscourse("loadIntoIdentityMap with post ids", function() {
|
|
var postStream = buildStream(1234);
|
|
expect(1);
|
|
|
|
this.stub(Discourse, "ajax").returns(Ember.RSVP.resolve({
|
|
post_stream: {
|
|
posts: [{id: 10, post_number: 10}]
|
|
}
|
|
}));
|
|
|
|
postStream.loadIntoIdentityMap([10]).then(function() {
|
|
present(postStream.findLoadedPost(10), "it adds the returned post to the store");
|
|
start();
|
|
});
|
|
});
|
|
|
|
asyncTestDiscourse("loading a post's history", function() {
|
|
var postStream = buildStream(1234);
|
|
expect(3);
|
|
|
|
var post = Discourse.Post.create({id: 4321});
|
|
|
|
var secondPost = Discourse.Post.create({id: 2222});
|
|
|
|
this.stub(Discourse, "ajax").returns(Ember.RSVP.resolve([secondPost]));
|
|
postStream.findReplyHistory(post).then(function() {
|
|
ok(Discourse.ajax.calledOnce, "it made the ajax request");
|
|
present(postStream.findLoadedPost(2222), "it stores the returned post in the identity map");
|
|
present(post.get('replyHistory'), "it sets the replyHistory attribute for the post");
|
|
start();
|
|
});
|
|
});
|
|
|
|
test("staging and undoing a new post", function() {
|
|
var postStream = buildStream(10101, [1]);
|
|
postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1}));
|
|
|
|
var user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321});
|
|
var stagedPost = Discourse.Post.create({ raw: 'hello world this is my new post' });
|
|
|
|
var topic = postStream.get('topic');
|
|
topic.setProperties({
|
|
posts_count: 1,
|
|
highest_post_number: 1
|
|
});
|
|
|
|
// Stage the new post in the stream
|
|
var result = postStream.stagePost(stagedPost, user);
|
|
equal(result, true, "it returns true");
|
|
equal(topic.get('highest_post_number'), 2, "it updates the highest_post_number");
|
|
ok(postStream.get('loading'), "it is loading while the post is being staged");
|
|
|
|
equal(topic.get('posts_count'), 2, "it increases the post count");
|
|
present(topic.get('last_posted_at'), "it updates last_posted_at");
|
|
equal(topic.get('details.last_poster'), user, "it changes the last poster");
|
|
|
|
equal(stagedPost.get('topic'), topic, "it assigns the topic reference");
|
|
equal(stagedPost.get('post_number'), 2, "it is assigned the probable post_number");
|
|
equal(postStream.get('filteredPostsCount'), 1, "it retains the filteredPostsCount");
|
|
present(stagedPost.get('created_at'), "it is assigned a created date");
|
|
ok(postStream.get('posts').contains(stagedPost), "the post is added to the stream");
|
|
blank(stagedPost.get('id'), "the post has no id yet");
|
|
|
|
// Undoing a created post (there was an error)
|
|
postStream.undoPost(stagedPost);
|
|
|
|
ok(!postStream.get('loading'), "it is no longer loading");
|
|
equal(topic.get('highest_post_number'), 1, "it reverts the highest_post_number");
|
|
equal(topic.get('posts_count'), 1, "it reverts the post count");
|
|
equal(postStream.get('filteredPostsCount'), 1, "it retains the filteredPostsCount");
|
|
ok(!postStream.get('posts').contains(stagedPost), "the post is removed from the stream");
|
|
});
|
|
|
|
test("staging and committing a post", function() {
|
|
var postStream = buildStream(10101, [1]);
|
|
postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1}));
|
|
var user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321});
|
|
var stagedPost = Discourse.Post.create({ raw: 'hello world this is my new post' });
|
|
|
|
var topic = postStream.get('topic');
|
|
topic.set('posts_count', 1);
|
|
|
|
// Stage the new post in the stream
|
|
var result = postStream.stagePost(stagedPost, user);
|
|
equal(result, true, "it returns true");
|
|
|
|
ok(postStream.get('loading'), "it is loading while the post is being staged");
|
|
stagedPost.setProperties({ id: 1234, raw: "different raw value" });
|
|
equal(postStream.get('filteredPostsCount'), 1, "it retains the filteredPostsCount");
|
|
|
|
result = postStream.stagePost(stagedPost, user);
|
|
equal(result, false, "you can't stage a post while it is currently staging");
|
|
|
|
postStream.commitPost(stagedPost);
|
|
ok(postStream.get('posts').contains(stagedPost), "the post is still in the stream");
|
|
ok(!postStream.get('loading'), "it is no longer loading");
|
|
equal(postStream.get('filteredPostsCount'), 2, "it increases the filteredPostsCount");
|
|
|
|
var found = postStream.findLoadedPost(stagedPost.get('id'));
|
|
present(found, "the post is in the identity map");
|
|
ok(postStream.indexOf(stagedPost) > -1, "the post is in the stream");
|
|
equal(found.get('raw'), 'different raw value', 'it also updated the value in the stream');
|
|
|
|
});
|
|
|
|
test('triggerNewPostInStream', function() {
|
|
var postStream = buildStream(225566);
|
|
|
|
this.stub(postStream, 'appendMore');
|
|
this.stub(postStream, 'refresh');
|
|
|
|
postStream.triggerNewPostInStream(null);
|
|
ok(!postStream.appendMore.calledOnce, "asking for a null id does nothing");
|
|
|
|
postStream.toggleSummary();
|
|
postStream.triggerNewPostInStream(1);
|
|
ok(!postStream.appendMore.calledOnce, "it will not trigger when summary is active");
|
|
|
|
postStream.cancelFilter();
|
|
postStream.toggleParticipant('eviltrout');
|
|
postStream.triggerNewPostInStream(1);
|
|
ok(!postStream.appendMore.calledOnce, "it will not trigger when a participant filter is active");
|
|
|
|
postStream.cancelFilter();
|
|
postStream.triggerNewPostInStream(1);
|
|
ok(!postStream.appendMore.calledOnce, "it wont't delegate to appendMore because the last post is not loaded");
|
|
|
|
postStream.cancelFilter();
|
|
postStream.appendPost(Discourse.Post.create({id: 1, post_number: 2}));
|
|
postStream.triggerNewPostInStream(2);
|
|
ok(postStream.appendMore.calledOnce, "delegates to appendMore because the last post is loaded");
|
|
});
|
|
|
|
|
|
test("loadedAllPosts when the id changes", function() {
|
|
// This can happen in a race condition between staging a post and it coming through on the
|
|
// message bus. If the id of a post changes we should reconsider the loadedAllPosts property.
|
|
var postStream = buildStream(10101, [1, 2]);
|
|
var postWithoutId = Discourse.Post.create({ raw: 'hello world this is my new post' });
|
|
|
|
postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1}));
|
|
postStream.appendPost(postWithoutId);
|
|
ok(!postStream.get('loadedAllPosts'), 'the last post is not loaded');
|
|
|
|
postWithoutId.set('id', 2);
|
|
ok(postStream.get('loadedAllPosts'), 'the last post is loaded now that the post has an id');
|
|
});
|
|
|
|
test("comitting and triggerNewPostInStream race condition", function() {
|
|
var postStream = buildStream(4964);
|
|
|
|
postStream.appendPost(Discourse.Post.create({id: 1, post_number: 1}));
|
|
var user = Discourse.User.create({username: 'eviltrout', name: 'eviltrout', id: 321});
|
|
var stagedPost = Discourse.Post.create({ raw: 'hello world this is my new post' });
|
|
|
|
var result = postStream.stagePost(stagedPost, user);
|
|
equal(postStream.get('filteredPostsCount'), 0, "it has no filteredPostsCount yet");
|
|
stagedPost.set('id', 123);
|
|
|
|
this.stub(postStream, 'appendMore');
|
|
postStream.triggerNewPostInStream(123);
|
|
equal(postStream.get('filteredPostsCount'), 1, "it added the post");
|
|
|
|
postStream.commitPost(stagedPost);
|
|
equal(postStream.get('filteredPostsCount'), 1, "it does not add the same post twice");
|
|
});
|
|
|