1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-28 05:03:59 +03:00

Refactor the addition of events to rooms

... and add some sanity checks

Two things here:

1. Clean up the Room API for adding new events to the timeline. Where before
we had addEvents and addEventsToTimeline, whose purposes were unclear, we now
have addLiveEvents which must be used for adding events to the end of the live
timeline, and addEventsToTimeline which should be used for pagination (either
back-pagination of the live timeline, or pagination of an old timeline).

2. Add some sanity checks for the live timeline. Today we have seen problems
where somehow the live timeline had gained a forward pagination token, or the
live timeline had got joined to another timeline, leading to much confusion -
and I would like to notice these sooner.
This commit is contained in:
Richard van der Hoff
2016-04-14 17:03:25 +01:00
parent 90101c0340
commit d87e5471fa
6 changed files with 290 additions and 265 deletions

View File

@@ -2131,7 +2131,7 @@ MatrixClient.prototype.scrollback = function(room, limit, callback) {
return self._http.authedRequest(callback, "GET", path, params); return self._http.authedRequest(callback, "GET", path, params);
}).done(function(res) { }).done(function(res) {
var matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self)); var matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self));
room.addEventsToTimeline(matrixEvents, true); room.addEventsToTimeline(matrixEvents, true, room.getLiveTimeline());
room.oldState.paginationToken = res.end; room.oldState.paginationToken = res.end;
if (res.chunk.length === 0) { if (res.chunk.length === 0) {
room.oldState.paginationToken = null; room.oldState.paginationToken = null;

View File

@@ -26,6 +26,18 @@ var utils = require("../utils");
var ContentRepo = require("../content-repo"); var ContentRepo = require("../content-repo");
var EventTimeline = require("./event-timeline"); var EventTimeline = require("./event-timeline");
// var DEBUG_SCROLL = false;
var DEBUG_SCROLL = true;
if (DEBUG_SCROLL) {
// using bind means that we get to keep useful line numbers in the console
var debuglog = console.log.bind(console);
} else {
var debuglog = function () {};
}
function synthesizeReceipt(userId, event, receiptType) { function synthesizeReceipt(userId, event, receiptType) {
// console.log("synthesizing receipt for "+event.getId()); // console.log("synthesizing receipt for "+event.getId());
// This is really ugly because JS has no way to express an object literal // This is really ugly because JS has no way to express an object literal
@@ -416,8 +428,8 @@ Room.prototype.addTimeline = function() {
* (oldest) instead of the end (newest) of the timeline. If true, the oldest * (oldest) instead of the end (newest) of the timeline. If true, the oldest
* event will be the <b>last</b> element of 'events'. * event will be the <b>last</b> element of 'events'.
* *
* @param {module:models/event-timeline~EventTimeline=} timeline timeline to * @param {module:models/event-timeline~EventTimeline} timeline timeline to
* add events to. If not given, events will be added to the live timeline * add events to.
* *
* @param {string=} paginationToken token for the next batch of events * @param {string=} paginationToken token for the next batch of events
* *
@@ -427,13 +439,14 @@ Room.prototype.addTimeline = function() {
Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline, Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
timeline, paginationToken) { timeline, paginationToken) {
if (!timeline) { if (!timeline) {
timeline = this._liveTimeline; throw new Error("'timeline' not specified for "+
"Room.addEventsToTimeline");
} }
if (!toStartOfTimeline && timeline == this._liveTimeline) { if (!toStartOfTimeline && timeline == this._liveTimeline) {
// special treatment for live events throw new Error("Room.addEventsToTimeline cannot be used for adding " +
this._addLiveEvents(events); "events to the live timeline - use Room.addLiveEvents " +
return; "instead");
} }
var direction = toStartOfTimeline ? EventTimeline.BACKWARDS : var direction = toStartOfTimeline ? EventTimeline.BACKWARDS :
@@ -529,7 +542,7 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
lastEventWasNew = false; lastEventWasNew = false;
if (existingTimeline == timeline) { if (existingTimeline == timeline) {
console.log("Event " + eventId + " already in timeline " + timeline); debuglog("Event " + eventId + " already in timeline " + timeline);
continue; continue;
} }
@@ -545,10 +558,10 @@ Room.prototype.addEventsToTimeline = function(events, toStartOfTimeline,
// that would happen, so I'm going to ignore it for now. // that would happen, so I'm going to ignore it for now.
// //
if (existingTimeline == neighbour) { if (existingTimeline == neighbour) {
console.log("Event " + eventId + " in neighbouring timeline - " + debuglog("Event " + eventId + " in neighbouring timeline - " +
"switching to " + existingTimeline); "switching to " + existingTimeline);
} else { } else {
console.log("Event " + eventId + " already in a different " + debuglog("Event " + eventId + " already in a different " +
"timeline " + existingTimeline); "timeline " + existingTimeline);
} }
timeline = existingTimeline; timeline = existingTimeline;
@@ -601,23 +614,23 @@ Room.prototype._addEventToTimeline = function(event, timeline, toStartOfTimeline
/** /**
* Add some events to the end of this room's live timeline. Will fire * Add an event to the end of this room's live timeline. Will fire
* "Room.timeline" for each event added. * "Room.timeline"..
* *
* @param {MatrixEvent[]} events A list of events to add. * @param {MatrixEvent} event Event to be added
* @param {string?} duplicateStrategy 'ignore' or 'replace'
* @fires module:client~MatrixClient#event:"Room.timeline" * @fires module:client~MatrixClient#event:"Room.timeline"
* @private * @private
*/ */
Room.prototype._addLiveEvents = function(events) { Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
for (var i = 0; i < events.length; i++) { if (event.getType() === "m.room.redaction") {
if (events[i].getType() === "m.room.redaction") { var redactId = event.event.redacts;
var redactId = events[i].event.redacts;
// if we know about this event, redact its contents now. // if we know about this event, redact its contents now.
var redactedEvent = this.findEventById(redactId); var redactedEvent = this.findEventById(redactId);
if (redactedEvent) { if (redactedEvent) {
redactedEvent.makeRedacted(events[i]); redactedEvent.makeRedacted(event);
this.emit("Room.redaction", events[i], this); this.emit("Room.redaction", event, this);
// TODO: we stash user displaynames (among other things) in // TODO: we stash user displaynames (among other things) in
// RoomMember objects which are then attached to other events // RoomMember objects which are then attached to other events
@@ -631,29 +644,56 @@ Room.prototype._addLiveEvents = function(events) {
// this may be needed to trigger an update. // this may be needed to trigger an update.
} }
if (events[i].getUnsigned().transaction_id) { if (event.getUnsigned().transaction_id) {
var existingEvent = this._txnToEvent[events[i].getUnsigned().transaction_id]; var existingEvent = this._txnToEvent[event.getUnsigned().transaction_id];
if (existingEvent) { if (existingEvent) {
// remote echo of an event we sent earlier // remote echo of an event we sent earlier
this._handleRemoteEcho(events[i], existingEvent); this._handleRemoteEcho(event, existingEvent);
continue; return;
} }
} }
if (!this._eventIdToTimeline[events[i].getId()]) { var timeline = this._eventIdToTimeline[event.getId()];
// TODO: pass through filter to see if this should be added to the timeline. if (timeline) {
this._addEventToTimeline(events[i], this._liveTimeline, false); if(duplicateStrategy === "replace") {
debuglog("Room._addLiveEvent: replacing duplicate event "+
event.getId());
var tlEvents = timeline.getEvents();
for (var j = 0; j < tlEvents.length; j++) {
if (tlEvents[j].getId() === event.getId()) {
// still need to set the right metadata on this event
setEventMetadata(
event,
timeline.getState(EventTimeline.FORWARDS),
false
);
if (!tlEvents[j].encryptedType) {
tlEvents[j] = event;
} }
// XXX: we need to fire an event when this happens.
break;
}
}
} else {
debuglog("Room._addLiveEvent: ignoring duplicate event "+
event.getId());
}
return;
}
// TODO: pass through filter to see if this should be added to the timeline.
this._addEventToTimeline(event, this._liveTimeline, false);
// synthesize and inject implicit read receipts // synthesize and inject implicit read receipts
// Done after adding the event because otherwise the app would get a read receipt // Done after adding the event because otherwise the app would get a read receipt
// pointing to an event that wasn't yet in the timeline // pointing to an event that wasn't yet in the timeline
if (events[i].sender) { if (event.sender) {
this.addReceipt(synthesizeReceipt( this.addReceipt(synthesizeReceipt(
events[i].sender.userId, events[i], "m.read" event.sender.userId, event, "m.read"
), true); ), true);
} }
}
}; };
@@ -855,20 +895,36 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
* Add some events to this room. This can include state events, message * Add some events to this room. This can include state events, message
* events and typing notifications. These events are treated as "live" so * events and typing notifications. These events are treated as "live" so
* they will go to the end of the timeline. * they will go to the end of the timeline.
*
* @param {MatrixEvent[]} events A list of events to add. * @param {MatrixEvent[]} events A list of events to add.
*
* @param {string} duplicateStrategy Optional. Applies to events in the * @param {string} duplicateStrategy Optional. Applies to events in the
* timeline only. If this is not specified, no duplicate suppression is * timeline only. If this is 'replace' then if a duplicate is encountered, the
* performed (this improves performance). If this is 'replace' then if a * event passed to this function will replace the existing event in the
* duplicate is encountered, the event passed to this function will replace the * timeline. If this is not specified, or is 'ignore', then the event passed to
* existing event in the timeline. If this is 'ignore', then the event passed to
* this function will be ignored entirely, preserving the existing event in the * this function will be ignored entirely, preserving the existing event in the
* timeline. Events are identical based on their event ID <b>only</b>. * timeline. Events are identical based on their event ID <b>only</b>.
*
* @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'. * @throws If <code>duplicateStrategy</code> is not falsey, 'replace' or 'ignore'.
*/ */
Room.prototype.addEvents = function(events, duplicateStrategy) { Room.prototype.addLiveEvents = function(events, duplicateStrategy) {
if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) { if (duplicateStrategy && ["replace", "ignore"].indexOf(duplicateStrategy) === -1) {
throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'"); throw new Error("duplicateStrategy MUST be either 'replace' or 'ignore'");
} }
// sanity check that the live timeline is still live
if (this._liveTimeline.getPaginationToken(EventTimeline.FORWARDS)) {
throw new Error(
"live timeline is no longer live - it has a pagination token (" +
this._liveTimeline.getPaginationToken(EventTimeline.FORWARDS) + ")"
);
}
if (this._liveTimeline.getNeighbouringTimeline(EventTimeline.FORWARDS)) {
throw new Error(
"live timeline is no longer live - it has a neighbouring timeline"
);
}
for (var i = 0; i < events.length; i++) { for (var i = 0; i < events.length; i++) {
if (events[i].getType() === "m.typing") { if (events[i].getType() === "m.typing") {
this.currentState.setTypingEvent(events[i]); this.currentState.setTypingEvent(events[i]);
@@ -879,41 +935,9 @@ Room.prototype.addEvents = function(events, duplicateStrategy) {
// N.B. account_data is added directly by /sync to avoid // N.B. account_data is added directly by /sync to avoid
// having to maintain an event.isAccountData() here // having to maintain an event.isAccountData() here
else { else {
var timeline = this._eventIdToTimeline[events[i].getId()];
if (timeline && duplicateStrategy) {
// is there a duplicate?
var shouldIgnore = false;
var tlEvents = timeline.getEvents();
for (var j = 0; j < tlEvents.length; j++) {
if (tlEvents[j].getId() === events[i].getId()) {
if (duplicateStrategy === "replace") {
// still need to set the right metadata on this event
setEventMetadata(
events[i],
timeline.getState(EventTimeline.FORWARDS),
false
);
if (!tlEvents[j].encryptedType) {
tlEvents[j] = events[i];
}
// skip the insert so we don't add this event twice.
// Don't break in case we replace multiple events.
shouldIgnore = true;
}
else if (duplicateStrategy === "ignore") {
shouldIgnore = true;
break; // stop searching, we're skipping the insert
}
}
}
if (shouldIgnore) {
continue; // skip the insertion of this event.
}
}
// TODO: We should have a filter to say "only add state event // TODO: We should have a filter to say "only add state event
// types X Y Z to the timeline". // types X Y Z to the timeline".
this._addLiveEvents([events[i]]); this._addLiveEvent(events[i], duplicateStrategy);
} }
} }
}; };

View File

@@ -373,7 +373,7 @@ WebStorageStore.prototype.scrollback = function(room, limit) {
); );
room.addEventsToTimeline(utils.map(scrollback, function(e) { room.addEventsToTimeline(utils.map(scrollback, function(e) {
return new MatrixEvent(e); return new MatrixEvent(e);
}), true); }), true, room.getLiveTimeline());
this._tokens[room.storageToken] = { this._tokens[room.storageToken] = {
earliestIndex: earliestIndex earliestIndex: earliestIndex
@@ -594,7 +594,7 @@ function loadRoom(store, roomId, numEvents, tokenArray) {
index--; index--;
} }
// add events backwards to diverge old state correctly. // add events backwards to diverge old state correctly.
room.addEventsToTimeline(recentEvents.reverse(), true); room.addEventsToTimeline(recentEvents.reverse(), true, room.getLiveTimeline());
room.oldState.paginationToken = currentStateMap.pagination_token; room.oldState.paginationToken = currentStateMap.pagination_token;
// set the token data to let us know which index this room instance is at // set the token data to let us know which index this room instance is at
// for scrollback. // for scrollback.

View File

@@ -262,7 +262,8 @@ SyncApi.prototype.peek = function(roomId) {
// will overwrite the pagination token, so make sure it overwrites // will overwrite the pagination token, so make sure it overwrites
// it with the right thing. // it with the right thing.
peekRoom.addEventsToTimeline(messages.reverse(), true, peekRoom.addEventsToTimeline(messages.reverse(), true,
undefined, response.messages.start); peekRoom.getLiveTimeline(),
response.messages.start);
client.store.storeRoom(peekRoom); client.store.storeRoom(peekRoom);
client.emit("Room", peekRoom); client.emit("Room", peekRoom);
@@ -328,7 +329,7 @@ SyncApi.prototype._peekPoll = function(roomId, token) {
return e.room_id === roomId; return e.room_id === roomId;
}).map(self.client.getEventMapper()); }).map(self.client.getEventMapper());
var room = self.client.getRoom(roomId); var room = self.client.getRoom(roomId);
room.addEvents(events); room.addLiveEvents(events);
self._peekPoll(roomId, res.end); self._peekPoll(roomId, res.end);
}, function(err) { }, function(err) {
console.error("[%s] Peek poll failed: %s", roomId, err); console.error("[%s] Peek poll failed: %s", roomId, err);
@@ -717,7 +718,7 @@ SyncApi.prototype._processSyncResponse = function(syncToken, data) {
// XXX: should we be adding ephemeralEvents to the timeline? // XXX: should we be adding ephemeralEvents to the timeline?
// It feels like that for symmetry with room.addAccountData() // It feels like that for symmetry with room.addAccountData()
// there should be a room.addEphemeralEvents() or similar. // there should be a room.addEphemeralEvents() or similar.
room.addEvents(ephemeralEvents); room.addLiveEvents(ephemeralEvents);
// we deliberately don't add accountData to the timeline // we deliberately don't add accountData to the timeline
room.addAccountData(accountDataEvents); room.addAccountData(accountDataEvents);
@@ -993,7 +994,7 @@ SyncApi.prototype._processRoomEvents = function(room, stateEventList,
// execute the timeline events, this will begin to diverge the current state // execute the timeline events, this will begin to diverge the current state
// if the timeline has any state events in it. // if the timeline has any state events in it.
room.addEventsToTimeline(timelineEventList); room.addLiveEvents(timelineEventList);
}; };
/** /**

View File

@@ -34,7 +34,7 @@ describe("MatrixClient", function() {
it("should no-op if you've already joined a room", function() { it("should no-op if you've already joined a room", function() {
var roomId = "!foo:bar"; var roomId = "!foo:bar";
var room = new Room(roomId); var room = new Room(roomId);
room.addEvents([ room.addLiveEvents([
utils.mkMembership({ utils.mkMembership({
user: userId, room: roomId, mship: "join", event: true user: userId, room: roomId, mship: "join", event: true
}) })

View File

@@ -82,7 +82,7 @@ describe("Room", function() {
}); });
}); });
describe("addEvents", function() { describe("addLiveEvents", function() {
var events = [ var events = [
utils.mkMessage({ utils.mkMessage({
room: roomId, user: userA, msg: "changing room name", event: true room: roomId, user: userA, msg: "changing room name", event: true
@@ -100,12 +100,12 @@ describe("Room", function() {
user_ids: [userA] user_ids: [userA]
} }
}); });
room.addEvents([typing]); room.addLiveEvents([typing]);
expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing); expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing);
}); });
it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() { it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
expect(function() { room.addEvents(events, "foo"); }).toThrow(); expect(function() { room.addLiveEvents(events, "foo"); }).toThrow();
}); });
it("should replace a timeline event if dupe strategy is 'replace'", function() { it("should replace a timeline event if dupe strategy is 'replace'", function() {
@@ -114,9 +114,9 @@ describe("Room", function() {
room: roomId, user: userA, msg: "dupe", event: true room: roomId, user: userA, msg: "dupe", event: true
}); });
dupe.event.event_id = events[0].getId(); dupe.event.event_id = events[0].getId();
room.addEvents(events); room.addLiveEvents(events);
expect(room.timeline[0]).toEqual(events[0]); expect(room.timeline[0]).toEqual(events[0]);
room.addEvents([dupe], "replace"); room.addLiveEvents([dupe], "replace");
expect(room.timeline[0]).toEqual(dupe); expect(room.timeline[0]).toEqual(dupe);
}); });
@@ -126,39 +126,13 @@ describe("Room", function() {
room: roomId, user: userA, msg: "dupe", event: true room: roomId, user: userA, msg: "dupe", event: true
}); });
dupe.event.event_id = events[0].getId(); dupe.event.event_id = events[0].getId();
room.addEvents(events); room.addLiveEvents(events);
expect(room.timeline[0]).toEqual(events[0]); expect(room.timeline[0]).toEqual(events[0]);
room.addEvents([dupe], "ignore"); room.addLiveEvents([dupe], "ignore");
expect(room.timeline[0]).toEqual(events[0]); expect(room.timeline[0]).toEqual(events[0]);
}); });
});
describe("addEventsToTimeline", function() { it("should emit 'Room.timeline' events",
var events = [
utils.mkMessage({
room: roomId, user: userA, msg: "changing room name", event: true
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "New Room Name" }
})
];
it("should be able to add events to the end", function() {
room.addEventsToTimeline(events);
expect(room.timeline.length).toEqual(2);
expect(room.timeline[0]).toEqual(events[0]);
expect(room.timeline[1]).toEqual(events[1]);
});
it("should be able to add events to the start", function() {
room.addEventsToTimeline(events, true);
expect(room.timeline.length).toEqual(2);
expect(room.timeline[0]).toEqual(events[1]);
expect(room.timeline[1]).toEqual(events[0]);
});
it("should emit 'Room.timeline' events when added to the end",
function() { function() {
var callCount = 0; var callCount = 0;
room.on("Room.timeline", function(event, emitRoom, toStart) { room.on("Room.timeline", function(event, emitRoom, toStart) {
@@ -168,99 +142,10 @@ describe("Room", function() {
expect(emitRoom).toEqual(room); expect(emitRoom).toEqual(room);
expect(toStart).toBeFalsy(); expect(toStart).toBeFalsy();
}); });
room.addEventsToTimeline(events); room.addLiveEvents(events);
expect(callCount).toEqual(2); expect(callCount).toEqual(2);
}); });
it("should emit 'Room.timeline' events when added to the start",
function() {
var callCount = 0;
room.on("Room.timeline", function(event, emitRoom, toStart) {
callCount += 1;
expect(room.timeline.length).toEqual(callCount);
expect(event).toEqual(events[callCount - 1]);
expect(emitRoom).toEqual(room);
expect(toStart).toBe(true);
});
room.addEventsToTimeline(events, true);
expect(callCount).toEqual(2);
});
it("should set event.sender for new and old events", function() {
var sentinel = {
userId: userA,
membership: "join",
name: "Alice"
};
var oldSentinel = {
userId: userA,
membership: "join",
name: "Old Alice"
};
room.currentState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return oldSentinel;
}
return null;
});
var newEv = utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "New Room Name" }
});
var oldEv = utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "Old Room Name" }
});
room.addEventsToTimeline([newEv]);
expect(newEv.sender).toEqual(sentinel);
room.addEventsToTimeline([oldEv], true);
expect(oldEv.sender).toEqual(oldSentinel);
});
it("should set event.target for new and old m.room.member events",
function() {
var sentinel = {
userId: userA,
membership: "join",
name: "Alice"
};
var oldSentinel = {
userId: userA,
membership: "join",
name: "Old Alice"
};
room.currentState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return oldSentinel;
}
return null;
});
var newEv = utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true
});
var oldEv = utils.mkMembership({
room: roomId, mship: "ban", user: userB, skey: userA, event: true
});
room.addEventsToTimeline([newEv]);
expect(newEv.target).toEqual(sentinel);
room.addEventsToTimeline([oldEv], true);
expect(oldEv.target).toEqual(oldSentinel);
});
it("should call setStateEvents on the right RoomState with the right " + it("should call setStateEvents on the right RoomState with the right " +
"forwardLooking value for new events", function() { "forwardLooking value for new events", function() {
var events = [ var events = [
@@ -274,7 +159,7 @@ describe("Room", function() {
} }
}) })
]; ];
room.addEventsToTimeline(events); room.addLiveEvents(events);
expect(room.currentState.setStateEvents).toHaveBeenCalledWith( expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
[events[0]] [events[0]]
); );
@@ -286,33 +171,6 @@ describe("Room", function() {
expect(room.oldState.setStateEvents).not.toHaveBeenCalled(); expect(room.oldState.setStateEvents).not.toHaveBeenCalled();
}); });
it("should call setStateEvents on the right RoomState with the right " +
"forwardLooking value for old events", function() {
var events = [
utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userB, event: true,
content: {
name: "New room"
}
})
];
room.addEventsToTimeline(events, true);
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
[events[0]]
);
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
[events[1]]
);
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
});
it("should synthesize read receipts for the senders of events", function() { it("should synthesize read receipts for the senders of events", function() {
var sentinel = { var sentinel = {
userId: userA, userId: userA,
@@ -325,7 +183,7 @@ describe("Room", function() {
} }
return null; return null;
}); });
room.addEventsToTimeline(events); room.addLiveEvents(events);
expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId()); expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId());
}); });
@@ -370,13 +228,155 @@ describe("Room", function() {
expect(room.timeline.length).toEqual(1); expect(room.timeline.length).toEqual(1);
// then the remoteEvent // then the remoteEvent
room.addEventsToTimeline([remoteEvent]); room.addLiveEvents([remoteEvent]);
expect(room.timeline.length).toEqual(1); expect(room.timeline.length).toEqual(1);
expect(callCount).toEqual(2); expect(callCount).toEqual(2);
}); });
}); });
describe("addEventsToTimeline", function() {
var events = [
utils.mkMessage({
room: roomId, user: userA, msg: "changing room name", event: true
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "New Room Name" }
})
];
it("should not be able to add events to the end", function() {
expect(function() {
room.addEventsToTimeline(events, false, room.getLiveTimeline())
}).toThrow();
});
it("should be able to add events to the start", function() {
room.addEventsToTimeline(events, true, room.getLiveTimeline());
expect(room.timeline.length).toEqual(2);
expect(room.timeline[0]).toEqual(events[1]);
expect(room.timeline[1]).toEqual(events[0]);
});
it("should emit 'Room.timeline' events when added to the start",
function() {
var callCount = 0;
room.on("Room.timeline", function(event, emitRoom, toStart) {
callCount += 1;
expect(room.timeline.length).toEqual(callCount);
expect(event).toEqual(events[callCount - 1]);
expect(emitRoom).toEqual(room);
expect(toStart).toBe(true);
});
room.addEventsToTimeline(events, true, room.getLiveTimeline());
expect(callCount).toEqual(2);
});
});
describe("event metadata handling", function() {
it("should set event.sender for new and old events", function() {
var sentinel = {
userId: userA,
membership: "join",
name: "Alice"
};
var oldSentinel = {
userId: userA,
membership: "join",
name: "Old Alice"
};
room.currentState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return oldSentinel;
}
return null;
});
var newEv = utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "New Room Name" }
});
var oldEv = utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
content: { name: "Old Room Name" }
});
room.addLiveEvents([newEv]);
expect(newEv.sender).toEqual(sentinel);
room.addEventsToTimeline([oldEv], true, room.getLiveTimeline());
expect(oldEv.sender).toEqual(oldSentinel);
});
it("should set event.target for new and old m.room.member events",
function() {
var sentinel = {
userId: userA,
membership: "join",
name: "Alice"
};
var oldSentinel = {
userId: userA,
membership: "join",
name: "Old Alice"
};
room.currentState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
room.oldState.getSentinelMember.andCallFake(function(uid) {
if (uid === userA) {
return oldSentinel;
}
return null;
});
var newEv = utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true
});
var oldEv = utils.mkMembership({
room: roomId, mship: "ban", user: userB, skey: userA, event: true
});
room.addLiveEvents([newEv]);
expect(newEv.target).toEqual(sentinel);
room.addEventsToTimeline([oldEv], true, room.getLiveTimeline());
expect(oldEv.target).toEqual(oldSentinel);
});
it("should call setStateEvents on the right RoomState with the right " +
"forwardLooking value for old events", function() {
var events = [
utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userB, event: true,
content: {
name: "New room"
}
})
];
room.addEventsToTimeline(events, true, room.getLiveTimeline());
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
[events[0]]
);
expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
[events[1]]
);
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(room.currentState.setStateEvents).not.toHaveBeenCalled();
});
});
var resetTimelineTests = function(timelineSupport) { var resetTimelineTests = function(timelineSupport) {
var events = [ var events = [
utils.mkMessage({ utils.mkMessage({
@@ -397,11 +397,11 @@ describe("Room", function() {
}); });
it("should copy state from previous timeline", function() { it("should copy state from previous timeline", function() {
room.addEventsToTimeline([events[0], events[1]]); room.addLiveEvents([events[0], events[1]]);
expect(room.getLiveTimeline().getEvents().length).toEqual(2); expect(room.getLiveTimeline().getEvents().length).toEqual(2);
room.resetLiveTimeline(); room.resetLiveTimeline();
room.addEventsToTimeline([events[2]]); room.addLiveEvents([events[2]]);
var oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS); var oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
var newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS); var newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
expect(room.getLiveTimeline().getEvents().length).toEqual(1); expect(room.getLiveTimeline().getEvents().length).toEqual(1);
@@ -410,11 +410,11 @@ describe("Room", function() {
}); });
it("should reset the legacy timeline fields", function() { it("should reset the legacy timeline fields", function() {
room.addEventsToTimeline([events[0], events[1]]); room.addLiveEvents([events[0], events[1]]);
expect(room.timeline.length).toEqual(2); expect(room.timeline.length).toEqual(2);
room.resetLiveTimeline(); room.resetLiveTimeline();
room.addEventsToTimeline([events[2]]); room.addLiveEvents([events[2]]);
var newLiveTimeline = room.getLiveTimeline(); var newLiveTimeline = room.getLiveTimeline();
expect(room.timeline).toEqual(newLiveTimeline.getEvents()); expect(room.timeline).toEqual(newLiveTimeline.getEvents());
expect(room.oldState).toEqual( expect(room.oldState).toEqual(
@@ -443,7 +443,7 @@ describe("Room", function() {
it("should " + (timelineSupport ? "remember" : "forget") + it("should " + (timelineSupport ? "remember" : "forget") +
" old timelines", function() { " old timelines", function() {
room.addEventsToTimeline([events[0]]); room.addLiveEvents([events[0]]);
expect(room.timeline.length).toEqual(1); expect(room.timeline.length).toEqual(1);
var firstLiveTimeline = room.getLiveTimeline(); var firstLiveTimeline = room.getLiveTimeline();
room.resetLiveTimeline(); room.resetLiveTimeline();
@@ -477,7 +477,7 @@ describe("Room", function() {
]; ];
it("should handle events in the same timeline", function() { it("should handle events in the same timeline", function() {
room.addEventsToTimeline(events); room.addLiveEvents(events);
expect(room.compareEventOrdering(events[0].getId(), expect(room.compareEventOrdering(events[0].getId(),
events[1].getId())) events[1].getId()))
@@ -496,7 +496,7 @@ describe("Room", function() {
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b'); room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b');
room.addEventsToTimeline([events[0]], false, oldTimeline); room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addEventsToTimeline([events[1]]); room.addLiveEvents([events[1]]);
expect(room.compareEventOrdering(events[0].getId(), expect(room.compareEventOrdering(events[0].getId(),
events[1].getId())) events[1].getId()))
@@ -510,7 +510,7 @@ describe("Room", function() {
var oldTimeline = room.addTimeline(); var oldTimeline = room.addTimeline();
room.addEventsToTimeline([events[0]], false, oldTimeline); room.addEventsToTimeline([events[0]], false, oldTimeline);
room.addEventsToTimeline([events[1]]); room.addLiveEvents([events[1]]);
expect(room.compareEventOrdering(events[0].getId(), expect(room.compareEventOrdering(events[0].getId(),
events[1].getId())) events[1].getId()))
@@ -521,7 +521,7 @@ describe("Room", function() {
}); });
it("should return null for unknown events", function() { it("should return null for unknown events", function() {
room.addEventsToTimeline(events); room.addLiveEvents(events);
expect(room.compareEventOrdering(events[0].getId(), "xxx")) expect(room.compareEventOrdering(events[0].getId(), "xxx"))
.toBe(null); .toBe(null);
@@ -1068,7 +1068,7 @@ describe("Room", function() {
}), }),
]; ];
room.addEventsToTimeline(events); room.addLiveEvents(events);
var ts = 13787898424; var ts = 13787898424;
// check it initialises correctly // check it initialises correctly
@@ -1159,9 +1159,9 @@ describe("Room", function() {
var eventC = utils.mkMessage({ var eventC = utils.mkMessage({
room: roomId, user: userA, msg: "remote 2", event: true room: roomId, user: userA, msg: "remote 2", event: true
}); });
room.addEvents([eventA]); room.addLiveEvents([eventA]);
room.addPendingEvent(eventB, "TXN1"); room.addPendingEvent(eventB, "TXN1");
room.addEvents([eventC]); room.addLiveEvents([eventC]);
expect(room.timeline).toEqual( expect(room.timeline).toEqual(
[eventA, eventC] [eventA, eventC]
); );
@@ -1185,9 +1185,9 @@ describe("Room", function() {
var eventC = utils.mkMessage({ var eventC = utils.mkMessage({
room: roomId, user: userA, msg: "remote 2", event: true room: roomId, user: userA, msg: "remote 2", event: true
}); });
room.addEvents([eventA]); room.addLiveEvents([eventA]);
room.addPendingEvent(eventB, "TXN1"); room.addPendingEvent(eventB, "TXN1");
room.addEvents([eventC]); room.addLiveEvents([eventC]);
expect(room.timeline).toEqual( expect(room.timeline).toEqual(
[eventA, eventB, eventC] [eventA, eventB, eventC]
); );