mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-06-02 18:41:37 +03:00
Much of this transformation has been done automatically: * add expect import to each file * replace `not.to` with `toNot` * replace `to[Not]Be{Undefined,Null}` with equivalents * replace `jasmine.createSpy(...)` with `except.createSpy`, and `andCallFake` with `andCall` Also: * replace `jasmine.createSpyObj` with manual alternatives * replace `jasmine.Clock` with `lolex`
1273 lines
49 KiB
JavaScript
1273 lines
49 KiB
JavaScript
"use strict";
|
|
import 'source-map-support/register';
|
|
const sdk = require("../..");
|
|
const Room = sdk.Room;
|
|
const RoomState = sdk.RoomState;
|
|
const MatrixEvent = sdk.MatrixEvent;
|
|
const EventStatus = sdk.EventStatus;
|
|
const EventTimeline = sdk.EventTimeline;
|
|
const utils = require("../test-utils");
|
|
|
|
import expect from 'expect';
|
|
|
|
describe("Room", function() {
|
|
const roomId = "!foo:bar";
|
|
const userA = "@alice:bar";
|
|
const userB = "@bertha:bar";
|
|
const userC = "@clarissa:bar";
|
|
const userD = "@dorothy:bar";
|
|
let room;
|
|
|
|
beforeEach(function() {
|
|
utils.beforeEach(this); // eslint-disable-line no-invalid-this
|
|
room = new Room(roomId);
|
|
// mock RoomStates
|
|
room.oldState = room.getLiveTimeline()._startState =
|
|
utils.mock(sdk.RoomState, "oldState");
|
|
room.currentState = room.getLiveTimeline()._endState =
|
|
utils.mock(sdk.RoomState, "currentState");
|
|
});
|
|
|
|
describe("getAvatarUrl", function() {
|
|
const hsUrl = "https://my.home.server";
|
|
|
|
it("should return the URL from m.room.avatar preferentially", function() {
|
|
room.currentState.getStateEvents.andCall(function(type, key) {
|
|
if (type === "m.room.avatar" && key === "") {
|
|
return utils.mkEvent({
|
|
event: true,
|
|
type: "m.room.avatar",
|
|
skey: "",
|
|
room: roomId,
|
|
user: userA,
|
|
content: {
|
|
url: "mxc://flibble/wibble",
|
|
},
|
|
});
|
|
}
|
|
});
|
|
const url = room.getAvatarUrl(hsUrl);
|
|
// we don't care about how the mxc->http conversion is done, other
|
|
// than it contains the mxc body.
|
|
expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
|
|
});
|
|
|
|
it("should return an identicon HTTP URL if allowDefault was set and there " +
|
|
"was no m.room.avatar event", function() {
|
|
const url = room.getAvatarUrl(hsUrl, 64, 64, "crop", true);
|
|
expect(url.indexOf("http")).toEqual(0); // don't care about form
|
|
});
|
|
|
|
it("should return nothing if there is no m.room.avatar and allowDefault=false",
|
|
function() {
|
|
const url = room.getAvatarUrl(hsUrl, 64, 64, "crop", false);
|
|
expect(url).toEqual(null);
|
|
});
|
|
});
|
|
|
|
describe("getMember", function() {
|
|
beforeEach(function() {
|
|
// clobber members property with test data
|
|
room.currentState.members = {
|
|
"@alice:bar": {
|
|
userId: userA,
|
|
roomId: roomId,
|
|
},
|
|
};
|
|
});
|
|
|
|
it("should return null if the member isn't in current state", function() {
|
|
expect(room.getMember("@bar:foo")).toEqual(null);
|
|
});
|
|
|
|
it("should return the member from current state", function() {
|
|
expect(room.getMember(userA)).toNotEqual(null);
|
|
});
|
|
});
|
|
|
|
describe("addLiveEvents", function() {
|
|
const 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 call RoomState.setTypingEvent on m.typing events", function() {
|
|
room.currentState = utils.mock(RoomState);
|
|
const typing = utils.mkEvent({
|
|
room: roomId, type: "m.typing", event: true, content: {
|
|
user_ids: [userA],
|
|
},
|
|
});
|
|
room.addLiveEvents([typing]);
|
|
expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing);
|
|
});
|
|
|
|
it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
|
|
expect(function() {
|
|
room.addLiveEvents(events, "foo");
|
|
}).toThrow();
|
|
});
|
|
|
|
it("should replace a timeline event if dupe strategy is 'replace'", function() {
|
|
// make a duplicate
|
|
const dupe = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "dupe", event: true,
|
|
});
|
|
dupe.event.event_id = events[0].getId();
|
|
room.addLiveEvents(events);
|
|
expect(room.timeline[0]).toEqual(events[0]);
|
|
room.addLiveEvents([dupe], "replace");
|
|
expect(room.timeline[0]).toEqual(dupe);
|
|
});
|
|
|
|
it("should ignore a given dupe event if dupe strategy is 'ignore'", function() {
|
|
// make a duplicate
|
|
const dupe = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "dupe", event: true,
|
|
});
|
|
dupe.event.event_id = events[0].getId();
|
|
room.addLiveEvents(events);
|
|
expect(room.timeline[0]).toEqual(events[0]);
|
|
room.addLiveEvents([dupe], "ignore");
|
|
expect(room.timeline[0]).toEqual(events[0]);
|
|
});
|
|
|
|
it("should emit 'Room.timeline' events",
|
|
function() {
|
|
let 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).toBeFalsy();
|
|
});
|
|
room.addLiveEvents(events);
|
|
expect(callCount).toEqual(2);
|
|
});
|
|
|
|
it("should call setStateEvents on the right RoomState with the right " +
|
|
"forwardLooking value for new events", function() {
|
|
const 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.addLiveEvents(events);
|
|
expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
|
|
[events[0]],
|
|
);
|
|
expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
|
|
[events[1]],
|
|
);
|
|
expect(events[0].forwardLooking).toBe(true);
|
|
expect(events[1].forwardLooking).toBe(true);
|
|
expect(room.oldState.setStateEvents).toNotHaveBeenCalled();
|
|
});
|
|
|
|
it("should synthesize read receipts for the senders of events", function() {
|
|
const sentinel = {
|
|
userId: userA,
|
|
membership: "join",
|
|
name: "Alice",
|
|
};
|
|
room.currentState.getSentinelMember.andCall(function(uid) {
|
|
if (uid === userA) {
|
|
return sentinel;
|
|
}
|
|
return null;
|
|
});
|
|
room.addLiveEvents(events);
|
|
expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId());
|
|
});
|
|
|
|
it("should emit Room.localEchoUpdated when a local echo is updated", function() {
|
|
const localEvent = utils.mkMessage({
|
|
room: roomId, user: userA, event: true,
|
|
});
|
|
localEvent.status = EventStatus.SENDING;
|
|
const localEventId = localEvent.getId();
|
|
|
|
const remoteEvent = utils.mkMessage({
|
|
room: roomId, user: userA, event: true,
|
|
});
|
|
remoteEvent.event.unsigned = {transaction_id: "TXN_ID"};
|
|
const remoteEventId = remoteEvent.getId();
|
|
|
|
let callCount = 0;
|
|
room.on("Room.localEchoUpdated",
|
|
function(event, emitRoom, oldEventId, oldStatus) {
|
|
switch (callCount) {
|
|
case 0:
|
|
expect(event.getId()).toEqual(localEventId);
|
|
expect(event.status).toEqual(EventStatus.SENDING);
|
|
expect(emitRoom).toEqual(room);
|
|
expect(oldEventId).toBe(null);
|
|
expect(oldStatus).toBe(null);
|
|
break;
|
|
case 1:
|
|
expect(event.getId()).toEqual(remoteEventId);
|
|
expect(event.status).toBe(null);
|
|
expect(emitRoom).toEqual(room);
|
|
expect(oldEventId).toEqual(localEventId);
|
|
expect(oldStatus).toBe(EventStatus.SENDING);
|
|
break;
|
|
}
|
|
callCount += 1;
|
|
},
|
|
);
|
|
|
|
// first add the local echo
|
|
room.addPendingEvent(localEvent, "TXN_ID");
|
|
expect(room.timeline.length).toEqual(1);
|
|
|
|
// then the remoteEvent
|
|
room.addLiveEvents([remoteEvent]);
|
|
expect(room.timeline.length).toEqual(1);
|
|
|
|
expect(callCount).toEqual(2);
|
|
});
|
|
});
|
|
|
|
describe("addEventsToTimeline", function() {
|
|
const 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() {
|
|
let 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() {
|
|
const sentinel = {
|
|
userId: userA,
|
|
membership: "join",
|
|
name: "Alice",
|
|
};
|
|
const oldSentinel = {
|
|
userId: userA,
|
|
membership: "join",
|
|
name: "Old Alice",
|
|
};
|
|
room.currentState.getSentinelMember.andCall(function(uid) {
|
|
if (uid === userA) {
|
|
return sentinel;
|
|
}
|
|
return null;
|
|
});
|
|
room.oldState.getSentinelMember.andCall(function(uid) {
|
|
if (uid === userA) {
|
|
return oldSentinel;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const newEv = utils.mkEvent({
|
|
type: "m.room.name", room: roomId, user: userA, event: true,
|
|
content: { name: "New Room Name" },
|
|
});
|
|
const 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() {
|
|
const sentinel = {
|
|
userId: userA,
|
|
membership: "join",
|
|
name: "Alice",
|
|
};
|
|
const oldSentinel = {
|
|
userId: userA,
|
|
membership: "join",
|
|
name: "Old Alice",
|
|
};
|
|
room.currentState.getSentinelMember.andCall(function(uid) {
|
|
if (uid === userA) {
|
|
return sentinel;
|
|
}
|
|
return null;
|
|
});
|
|
room.oldState.getSentinelMember.andCall(function(uid) {
|
|
if (uid === userA) {
|
|
return oldSentinel;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const newEv = utils.mkMembership({
|
|
room: roomId, mship: "invite", user: userB, skey: userA, event: true,
|
|
});
|
|
const 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() {
|
|
const 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).toNotHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
const resetTimelineTests = function(timelineSupport) {
|
|
const events = [
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "A message", event: true,
|
|
}),
|
|
utils.mkEvent({
|
|
type: "m.room.name", room: roomId, user: userA, event: true,
|
|
content: { name: "New Room Name" },
|
|
}),
|
|
utils.mkEvent({
|
|
type: "m.room.name", room: roomId, user: userA, event: true,
|
|
content: { name: "Another New Name" },
|
|
}),
|
|
];
|
|
|
|
beforeEach(function() {
|
|
room = new Room(roomId, {timelineSupport: timelineSupport});
|
|
});
|
|
|
|
it("should copy state from previous timeline", function() {
|
|
room.addLiveEvents([events[0], events[1]]);
|
|
expect(room.getLiveTimeline().getEvents().length).toEqual(2);
|
|
room.resetLiveTimeline();
|
|
|
|
room.addLiveEvents([events[2]]);
|
|
const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
|
|
const newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
|
expect(room.getLiveTimeline().getEvents().length).toEqual(1);
|
|
expect(oldState.getStateEvents("m.room.name", "")).toEqual(events[1]);
|
|
expect(newState.getStateEvents("m.room.name", "")).toEqual(events[2]);
|
|
});
|
|
|
|
it("should reset the legacy timeline fields", function() {
|
|
room.addLiveEvents([events[0], events[1]]);
|
|
expect(room.timeline.length).toEqual(2);
|
|
room.resetLiveTimeline();
|
|
|
|
room.addLiveEvents([events[2]]);
|
|
const newLiveTimeline = room.getLiveTimeline();
|
|
expect(room.timeline).toEqual(newLiveTimeline.getEvents());
|
|
expect(room.oldState).toEqual(
|
|
newLiveTimeline.getState(EventTimeline.BACKWARDS));
|
|
expect(room.currentState).toEqual(
|
|
newLiveTimeline.getState(EventTimeline.FORWARDS));
|
|
});
|
|
|
|
it("should emit Room.timelineReset event and set the correct " +
|
|
"pagination token", function() {
|
|
let callCount = 0;
|
|
room.on("Room.timelineReset", function(emitRoom) {
|
|
callCount += 1;
|
|
expect(emitRoom).toEqual(room);
|
|
|
|
// make sure that the pagination token has been set before the
|
|
// event is emitted.
|
|
const tok = emitRoom.getLiveTimeline()
|
|
.getPaginationToken(EventTimeline.BACKWARDS);
|
|
|
|
expect(tok).toEqual("pagToken");
|
|
});
|
|
room.resetLiveTimeline("pagToken");
|
|
expect(callCount).toEqual(1);
|
|
});
|
|
|
|
it("should " + (timelineSupport ? "remember" : "forget") +
|
|
" old timelines", function() {
|
|
room.addLiveEvents([events[0]]);
|
|
expect(room.timeline.length).toEqual(1);
|
|
const firstLiveTimeline = room.getLiveTimeline();
|
|
room.resetLiveTimeline();
|
|
|
|
const tl = room.getTimelineForEvent(events[0].getId());
|
|
expect(tl).toBe(timelineSupport ? firstLiveTimeline : null);
|
|
});
|
|
};
|
|
|
|
describe("resetLiveTimeline with timelinesupport enabled",
|
|
resetTimelineTests.bind(null, true));
|
|
describe("resetLiveTimeline with timelinesupport disabled",
|
|
resetTimelineTests.bind(null, false));
|
|
|
|
describe("compareEventOrdering", function() {
|
|
beforeEach(function() {
|
|
room = new Room(roomId, {timelineSupport: true});
|
|
});
|
|
|
|
const events = [
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "1111", event: true,
|
|
}),
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "2222", event: true,
|
|
}),
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "3333", event: true,
|
|
}),
|
|
];
|
|
|
|
it("should handle events in the same timeline", function() {
|
|
room.addLiveEvents(events);
|
|
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
|
|
events[1].getId()))
|
|
.toBeLessThan(0);
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId(),
|
|
events[1].getId()))
|
|
.toBeGreaterThan(0);
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
|
|
events[1].getId()))
|
|
.toEqual(0);
|
|
});
|
|
|
|
it("should handle events in adjacent timelines", function() {
|
|
const oldTimeline = room.addTimeline();
|
|
oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), 'f');
|
|
room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b');
|
|
|
|
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
|
room.addLiveEvents([events[1]]);
|
|
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
|
|
events[1].getId()))
|
|
.toBeLessThan(0);
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
|
|
events[0].getId()))
|
|
.toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should return null for events in non-adjacent timelines", function() {
|
|
const oldTimeline = room.addTimeline();
|
|
|
|
room.addEventsToTimeline([events[0]], false, oldTimeline);
|
|
room.addLiveEvents([events[1]]);
|
|
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
|
|
events[1].getId()))
|
|
.toBe(null);
|
|
expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
|
|
events[0].getId()))
|
|
.toBe(null);
|
|
});
|
|
|
|
it("should return null for unknown events", function() {
|
|
room.addLiveEvents(events);
|
|
|
|
expect(room.getUnfilteredTimelineSet()
|
|
.compareEventOrdering(events[0].getId(), "xxx"))
|
|
.toBe(null);
|
|
expect(room.getUnfilteredTimelineSet()
|
|
.compareEventOrdering("xxx", events[0].getId()))
|
|
.toBe(null);
|
|
expect(room.getUnfilteredTimelineSet()
|
|
.compareEventOrdering(events[0].getId(), events[0].getId()))
|
|
.toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("getJoinedMembers", function() {
|
|
it("should return members whose membership is 'join'", function() {
|
|
room.currentState.getMembers.andCall(function() {
|
|
return [
|
|
{ userId: "@alice:bar", membership: "join" },
|
|
{ userId: "@bob:bar", membership: "invite" },
|
|
{ userId: "@cleo:bar", membership: "leave" },
|
|
];
|
|
});
|
|
const res = room.getJoinedMembers();
|
|
expect(res.length).toEqual(1);
|
|
expect(res[0].userId).toEqual("@alice:bar");
|
|
});
|
|
|
|
it("should return an empty list if no membership is 'join'", function() {
|
|
room.currentState.getMembers.andCall(function() {
|
|
return [
|
|
{ userId: "@bob:bar", membership: "invite" },
|
|
];
|
|
});
|
|
const res = room.getJoinedMembers();
|
|
expect(res.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe("hasMembershipState", function() {
|
|
it("should return true for a matching userId and membership",
|
|
function() {
|
|
room.currentState.members = {
|
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
|
"@bob:bar": { userId: "@bob:bar", membership: "invite" },
|
|
};
|
|
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true);
|
|
});
|
|
|
|
it("should return false if match membership but no match userId",
|
|
function() {
|
|
room.currentState.members = {
|
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
|
};
|
|
expect(room.hasMembershipState("@bob:bar", "join")).toBe(false);
|
|
});
|
|
|
|
it("should return false if match userId but no match membership",
|
|
function() {
|
|
room.currentState.members = {
|
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
|
};
|
|
expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false);
|
|
});
|
|
|
|
it("should return false if no match membership or userId",
|
|
function() {
|
|
room.currentState.members = {
|
|
"@alice:bar": { userId: "@alice:bar", membership: "join" },
|
|
};
|
|
expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false);
|
|
});
|
|
|
|
it("should return false if no members exist",
|
|
function() {
|
|
room.currentState.members = {};
|
|
expect(room.hasMembershipState("@foo:bar", "join")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("recalculate", function() {
|
|
let stateLookup = {
|
|
// event.type + "$" event.state_key : MatrixEvent
|
|
};
|
|
|
|
const setJoinRule = function(rule) {
|
|
stateLookup["m.room.join_rules$"] = utils.mkEvent({
|
|
type: "m.room.join_rules", room: roomId, user: userA, content: {
|
|
join_rule: rule,
|
|
}, event: true,
|
|
});
|
|
};
|
|
const setAliases = function(aliases, stateKey) {
|
|
if (!stateKey) {
|
|
stateKey = "flibble";
|
|
}
|
|
stateLookup["m.room.aliases$" + stateKey] = utils.mkEvent({
|
|
type: "m.room.aliases", room: roomId, skey: stateKey, content: {
|
|
aliases: aliases,
|
|
}, event: true,
|
|
});
|
|
};
|
|
const setRoomName = function(name) {
|
|
stateLookup["m.room.name$"] = utils.mkEvent({
|
|
type: "m.room.name", room: roomId, user: userA, content: {
|
|
name: name,
|
|
}, event: true,
|
|
});
|
|
};
|
|
const addMember = function(userId, state, opts) {
|
|
if (!state) {
|
|
state = "join";
|
|
}
|
|
opts = opts || {};
|
|
opts.room = roomId;
|
|
opts.mship = state;
|
|
opts.user = opts.user || userId;
|
|
opts.skey = userId;
|
|
opts.event = true;
|
|
stateLookup["m.room.member$" + userId] = utils.mkMembership(opts);
|
|
};
|
|
|
|
beforeEach(function() {
|
|
stateLookup = {};
|
|
room.currentState.getStateEvents.andCall(function(type, key) {
|
|
if (key === undefined) {
|
|
const prefix = type + "$";
|
|
const list = [];
|
|
for (const stateBlob in stateLookup) {
|
|
if (!stateLookup.hasOwnProperty(stateBlob)) {
|
|
continue;
|
|
}
|
|
if (stateBlob.indexOf(prefix) === 0) {
|
|
list.push(stateLookup[stateBlob]);
|
|
}
|
|
}
|
|
return list;
|
|
} else {
|
|
return stateLookup[type + "$" + key];
|
|
}
|
|
});
|
|
room.currentState.getMembers.andCall(function() {
|
|
const memberEvents = room.currentState.getStateEvents("m.room.member");
|
|
const members = [];
|
|
for (let i = 0; i < memberEvents.length; i++) {
|
|
members.push({
|
|
name: memberEvents[i].event.content &&
|
|
memberEvents[i].event.content.displayname ?
|
|
memberEvents[i].event.content.displayname :
|
|
memberEvents[i].getStateKey(),
|
|
userId: memberEvents[i].getStateKey(),
|
|
events: { member: memberEvents[i] },
|
|
});
|
|
}
|
|
return members;
|
|
});
|
|
room.currentState.getMember.andCall(function(userId) {
|
|
const memberEvent = room.currentState.getStateEvents(
|
|
"m.room.member", userId,
|
|
);
|
|
return {
|
|
name: memberEvent.event.content &&
|
|
memberEvent.event.content.displayname ?
|
|
memberEvent.event.content.displayname :
|
|
memberEvent.getStateKey(),
|
|
userId: memberEvent.getStateKey(),
|
|
events: { member: memberEvent },
|
|
};
|
|
});
|
|
});
|
|
|
|
describe("Room.recalculate => Stripped State Events", function() {
|
|
it("should set stripped state events as actual state events if the " +
|
|
"room is an invite room", function() {
|
|
const roomName = "flibble";
|
|
|
|
addMember(userA, "invite");
|
|
stateLookup["m.room.member$" + userA].event.invite_room_state = [
|
|
{
|
|
type: "m.room.name",
|
|
state_key: "",
|
|
content: {
|
|
name: roomName,
|
|
},
|
|
},
|
|
];
|
|
|
|
room.recalculate(userA);
|
|
expect(room.currentState.setStateEvents).toHaveBeenCalled();
|
|
// first call, first arg (which is an array), first element in array
|
|
const fakeEvent = room.currentState.setStateEvents.calls[0].
|
|
arguments[0][0];
|
|
expect(fakeEvent.getContent()).toEqual({
|
|
name: roomName,
|
|
});
|
|
});
|
|
|
|
it("should not clobber state events if it isn't an invite room", function() {
|
|
addMember(userA, "join");
|
|
stateLookup["m.room.member$" + userA].event.invite_room_state = [
|
|
{
|
|
type: "m.room.name",
|
|
state_key: "",
|
|
content: {
|
|
name: "flibble",
|
|
},
|
|
},
|
|
];
|
|
|
|
room.recalculate(userA);
|
|
expect(room.currentState.setStateEvents).toNotHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Room.recalculate => Room Name", function() {
|
|
it("should return the names of members in a private (invite join_rules)" +
|
|
" room if a room name and alias don't exist and there are >3 members.",
|
|
function() {
|
|
setJoinRule("invite");
|
|
addMember(userA);
|
|
addMember(userB);
|
|
addMember(userC);
|
|
addMember(userD);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
// we expect at least 1 member to be mentioned
|
|
const others = [userB, userC, userD];
|
|
let found = false;
|
|
for (let i = 0; i < others.length; i++) {
|
|
if (name.indexOf(others[i]) !== -1) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
expect(found).toEqual(true, name);
|
|
});
|
|
|
|
it("should return the names of members in a private (invite join_rules)" +
|
|
" room if a room name and alias don't exist and there are >2 members.",
|
|
function() {
|
|
setJoinRule("invite");
|
|
addMember(userA);
|
|
addMember(userB);
|
|
addMember(userC);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name.indexOf(userB)).toNotEqual(-1, name);
|
|
expect(name.indexOf(userC)).toNotEqual(-1, name);
|
|
});
|
|
|
|
it("should return the names of members in a public (public join_rules)" +
|
|
" room if a room name and alias don't exist and there are >2 members.",
|
|
function() {
|
|
setJoinRule("public");
|
|
addMember(userA);
|
|
addMember(userB);
|
|
addMember(userC);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name.indexOf(userB)).toNotEqual(-1, name);
|
|
expect(name.indexOf(userC)).toNotEqual(-1, name);
|
|
});
|
|
|
|
it("should show the other user's name for public (public join_rules)" +
|
|
" rooms if a room name and alias don't exist and it is a 1:1-chat.",
|
|
function() {
|
|
setJoinRule("public");
|
|
addMember(userA);
|
|
addMember(userB);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name.indexOf(userB)).toNotEqual(-1, name);
|
|
});
|
|
|
|
it("should show the other user's name for private " +
|
|
"(invite join_rules) rooms if a room name and alias don't exist and it" +
|
|
" is a 1:1-chat.", function() {
|
|
setJoinRule("invite");
|
|
addMember(userA);
|
|
addMember(userB);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name.indexOf(userB)).toNotEqual(-1, name);
|
|
});
|
|
|
|
it("should show the other user's name for private" +
|
|
" (invite join_rules) rooms if you are invited to it.", function() {
|
|
setJoinRule("invite");
|
|
addMember(userA, "invite", {user: userB});
|
|
addMember(userB);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name.indexOf(userB)).toNotEqual(-1, name);
|
|
});
|
|
|
|
it("should show the room alias if one exists for private " +
|
|
"(invite join_rules) rooms if a room name doesn't exist.", function() {
|
|
const alias = "#room_alias:here";
|
|
setJoinRule("invite");
|
|
setAliases([alias, "#another:one"]);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual(alias);
|
|
});
|
|
|
|
it("should show the room alias if one exists for public " +
|
|
"(public join_rules) rooms if a room name doesn't exist.", function() {
|
|
const alias = "#room_alias:here";
|
|
setJoinRule("public");
|
|
setAliases([alias, "#another:one"]);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual(alias);
|
|
});
|
|
|
|
it("should show the room name if one exists for private " +
|
|
"(invite join_rules) rooms.", function() {
|
|
const roomName = "A mighty name indeed";
|
|
setJoinRule("invite");
|
|
setRoomName(roomName);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual(roomName);
|
|
});
|
|
|
|
it("should show the room name if one exists for public " +
|
|
"(public join_rules) rooms.", function() {
|
|
const roomName = "A mighty name indeed";
|
|
setJoinRule("public");
|
|
setRoomName(roomName);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual(roomName);
|
|
});
|
|
|
|
it("should return 'Empty room' for private (invite join_rules) rooms if" +
|
|
" a room name and alias don't exist and it is a self-chat.", function() {
|
|
setJoinRule("invite");
|
|
addMember(userA);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual("Empty room");
|
|
});
|
|
|
|
it("should return 'Empty room' for public (public join_rules) rooms if a" +
|
|
" room name and alias don't exist and it is a self-chat.", function() {
|
|
setJoinRule("public");
|
|
addMember(userA);
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual("Empty room");
|
|
});
|
|
|
|
it("should return 'Empty room' if there is no name, " +
|
|
"alias or members in the room.",
|
|
function() {
|
|
room.recalculate(userA);
|
|
const name = room.name;
|
|
expect(name).toEqual("Empty room");
|
|
});
|
|
|
|
it("should return 'Invite from [inviter display name] if state event " +
|
|
"available",
|
|
function() {
|
|
setJoinRule("invite");
|
|
addMember(userA, 'join', {name: "Alice"});
|
|
addMember(userB, "invite", {user: userA});
|
|
room.recalculate(userB);
|
|
const name = room.name;
|
|
expect(name).toEqual("Invite from Alice");
|
|
});
|
|
|
|
it("should return inviter mxid if display name not available",
|
|
function() {
|
|
setJoinRule("invite");
|
|
addMember(userA);
|
|
addMember(userB, "invite", {user: userA});
|
|
room.recalculate(userB);
|
|
const name = room.name;
|
|
expect(name).toEqual("Invite from " + userA);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("receipts", function() {
|
|
const eventToAck = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "PLEASE ACKNOWLEDGE MY EXISTENCE",
|
|
event: true,
|
|
});
|
|
|
|
function mkReceipt(roomId, records) {
|
|
const content = {};
|
|
records.forEach(function(r) {
|
|
if (!content[r.eventId]) {
|
|
content[r.eventId] = {};
|
|
}
|
|
if (!content[r.eventId][r.type]) {
|
|
content[r.eventId][r.type] = {};
|
|
}
|
|
content[r.eventId][r.type][r.userId] = {
|
|
ts: r.ts,
|
|
};
|
|
});
|
|
return new MatrixEvent({
|
|
content: content,
|
|
room_id: roomId,
|
|
type: "m.receipt",
|
|
});
|
|
}
|
|
|
|
function mkRecord(eventId, type, userId, ts) {
|
|
ts = ts || Date.now();
|
|
return {
|
|
eventId: eventId,
|
|
type: type,
|
|
userId: userId,
|
|
ts: ts,
|
|
};
|
|
}
|
|
|
|
describe("addReceipt", function() {
|
|
it("should store the receipt so it can be obtained via getReceiptsForEvent",
|
|
function() {
|
|
const ts = 13787898424;
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
|
]));
|
|
expect(room.getReceiptsForEvent(eventToAck)).toEqual([{
|
|
type: "m.read",
|
|
userId: userB,
|
|
data: {
|
|
ts: ts,
|
|
},
|
|
}]);
|
|
});
|
|
|
|
it("should emit an event when a receipt is added",
|
|
function() {
|
|
const listener = expect.createSpy();
|
|
room.on("Room.receipt", listener);
|
|
|
|
const ts = 13787898424;
|
|
|
|
const receiptEvent = mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
|
]);
|
|
|
|
room.addReceipt(receiptEvent);
|
|
expect(listener).toHaveBeenCalledWith(receiptEvent, room);
|
|
});
|
|
|
|
it("should clobber receipts based on type and user ID", function() {
|
|
const nextEventToAck = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "I AM HERE YOU KNOW",
|
|
event: true,
|
|
});
|
|
const ts = 13787898424;
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
|
]));
|
|
const ts2 = 13787899999;
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(nextEventToAck.getId(), "m.read", userB, ts2),
|
|
]));
|
|
expect(room.getReceiptsForEvent(eventToAck)).toEqual([]);
|
|
expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{
|
|
type: "m.read",
|
|
userId: userB,
|
|
data: {
|
|
ts: ts2,
|
|
},
|
|
}]);
|
|
});
|
|
|
|
it("should persist multiple receipts for a single event ID", function() {
|
|
const ts = 13787898424;
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
|
mkRecord(eventToAck.getId(), "m.read", userC, ts),
|
|
mkRecord(eventToAck.getId(), "m.read", userD, ts),
|
|
]));
|
|
expect(room.getUsersReadUpTo(eventToAck)).toEqual(
|
|
[userB, userC, userD],
|
|
);
|
|
});
|
|
|
|
it("should persist multiple receipts for a single receipt type", function() {
|
|
const eventTwo = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "2222",
|
|
event: true,
|
|
});
|
|
const eventThree = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "3333",
|
|
event: true,
|
|
});
|
|
const ts = 13787898424;
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
|
mkRecord(eventTwo.getId(), "m.read", userC, ts),
|
|
mkRecord(eventThree.getId(), "m.read", userD, ts),
|
|
]));
|
|
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
|
|
expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]);
|
|
expect(room.getUsersReadUpTo(eventThree)).toEqual([userD]);
|
|
});
|
|
|
|
it("should persist multiple receipts for a single user ID", function() {
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.delivered", userB, 13787898424),
|
|
mkRecord(eventToAck.getId(), "m.read", userB, 22222222),
|
|
mkRecord(eventToAck.getId(), "m.seen", userB, 33333333),
|
|
]));
|
|
expect(room.getReceiptsForEvent(eventToAck)).toEqual([
|
|
{
|
|
type: "m.delivered",
|
|
userId: userB,
|
|
data: {
|
|
ts: 13787898424,
|
|
},
|
|
},
|
|
{
|
|
type: "m.read",
|
|
userId: userB,
|
|
data: {
|
|
ts: 22222222,
|
|
},
|
|
},
|
|
{
|
|
type: "m.seen",
|
|
userId: userB,
|
|
data: {
|
|
ts: 33333333,
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("should prioritise the most recent event", function() {
|
|
const events = [
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "1111",
|
|
event: true,
|
|
}),
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "2222",
|
|
event: true,
|
|
}),
|
|
utils.mkMessage({
|
|
room: roomId, user: userA, msg: "3333",
|
|
event: true,
|
|
}),
|
|
];
|
|
|
|
room.addLiveEvents(events);
|
|
const ts = 13787898424;
|
|
|
|
// check it initialises correctly
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(events[0].getId(), "m.read", userB, ts),
|
|
]));
|
|
expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId());
|
|
|
|
// 2>0, so it should move forward
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(events[2].getId(), "m.read", userB, ts),
|
|
]));
|
|
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
|
|
|
|
// 1<2, so it should stay put
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(events[1].getId(), "m.read", userB, ts),
|
|
]));
|
|
expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
|
|
});
|
|
});
|
|
|
|
describe("getUsersReadUpTo", function() {
|
|
it("should return user IDs read up to the given event", function() {
|
|
const ts = 13787898424;
|
|
room.addReceipt(mkReceipt(roomId, [
|
|
mkRecord(eventToAck.getId(), "m.read", userB, ts),
|
|
]));
|
|
expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("tags", function() {
|
|
function mkTags(roomId, tags) {
|
|
const content = { "tags": tags };
|
|
return new MatrixEvent({
|
|
content: content,
|
|
room_id: roomId,
|
|
type: "m.tag",
|
|
});
|
|
}
|
|
|
|
describe("addTag", function() {
|
|
it("should set tags on rooms from event stream so " +
|
|
"they can be obtained by the tags property",
|
|
function() {
|
|
const tags = { "m.foo": { "order": 0.5 } };
|
|
room.addTags(mkTags(roomId, tags));
|
|
expect(room.tags).toEqual(tags);
|
|
});
|
|
|
|
it("should emit Room.tags event when new tags are " +
|
|
"received on the event stream",
|
|
function() {
|
|
const listener = expect.createSpy();
|
|
room.on("Room.tags", listener);
|
|
|
|
const tags = { "m.foo": { "order": 0.5 } };
|
|
const event = mkTags(roomId, tags);
|
|
room.addTags(event);
|
|
expect(listener).toHaveBeenCalledWith(event, room);
|
|
});
|
|
|
|
// XXX: shouldn't we try injecting actual m.tag events onto the eventstream
|
|
// rather than injecting via room.addTags()?
|
|
});
|
|
});
|
|
|
|
describe("addPendingEvent", function() {
|
|
it("should add pending events to the pendingEventList if " +
|
|
"pendingEventOrdering == 'detached'", function() {
|
|
const room = new Room(roomId, {
|
|
pendingEventOrdering: "detached",
|
|
});
|
|
const eventA = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "remote 1", event: true,
|
|
});
|
|
const eventB = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "local 1", event: true,
|
|
});
|
|
eventB.status = EventStatus.SENDING;
|
|
const eventC = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "remote 2", event: true,
|
|
});
|
|
room.addLiveEvents([eventA]);
|
|
room.addPendingEvent(eventB, "TXN1");
|
|
room.addLiveEvents([eventC]);
|
|
expect(room.timeline).toEqual(
|
|
[eventA, eventC],
|
|
);
|
|
expect(room.getPendingEvents()).toEqual(
|
|
[eventB],
|
|
);
|
|
});
|
|
|
|
it("should add pending events to the timeline if " +
|
|
"pendingEventOrdering == 'chronological'", function() {
|
|
room = new Room(roomId, {
|
|
pendingEventOrdering: "chronological",
|
|
});
|
|
const eventA = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "remote 1", event: true,
|
|
});
|
|
const eventB = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "local 1", event: true,
|
|
});
|
|
eventB.status = EventStatus.SENDING;
|
|
const eventC = utils.mkMessage({
|
|
room: roomId, user: userA, msg: "remote 2", event: true,
|
|
});
|
|
room.addLiveEvents([eventA]);
|
|
room.addPendingEvent(eventB, "TXN1");
|
|
room.addLiveEvents([eventC]);
|
|
expect(room.timeline).toEqual(
|
|
[eventA, eventB, eventC],
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("updatePendingEvent", function() {
|
|
it("should remove cancelled events from the pending list", function() {
|
|
const room = new Room(roomId, {
|
|
pendingEventOrdering: "detached",
|
|
});
|
|
const eventA = utils.mkMessage({
|
|
room: roomId, user: userA, event: true,
|
|
});
|
|
eventA.status = EventStatus.SENDING;
|
|
const eventId = eventA.getId();
|
|
|
|
room.addPendingEvent(eventA, "TXN1");
|
|
expect(room.getPendingEvents()).toEqual(
|
|
[eventA],
|
|
);
|
|
|
|
// the event has to have been failed or queued before it can be
|
|
// cancelled
|
|
room.updatePendingEvent(eventA, EventStatus.NOT_SENT);
|
|
|
|
let callCount = 0;
|
|
room.on("Room.localEchoUpdated",
|
|
function(event, emitRoom, oldEventId, oldStatus) {
|
|
expect(event).toEqual(eventA);
|
|
expect(event.status).toEqual(EventStatus.CANCELLED);
|
|
expect(emitRoom).toEqual(room);
|
|
expect(oldEventId).toEqual(eventId);
|
|
expect(oldStatus).toEqual(EventStatus.NOT_SENT);
|
|
callCount++;
|
|
});
|
|
|
|
room.updatePendingEvent(eventA, EventStatus.CANCELLED);
|
|
expect(room.getPendingEvents()).toEqual([]);
|
|
expect(callCount).toEqual(1);
|
|
});
|
|
|
|
|
|
it("should remove cancelled events from the timeline", function() {
|
|
const room = new Room(roomId);
|
|
const eventA = utils.mkMessage({
|
|
room: roomId, user: userA, event: true,
|
|
});
|
|
eventA.status = EventStatus.SENDING;
|
|
const eventId = eventA.getId();
|
|
|
|
room.addPendingEvent(eventA, "TXN1");
|
|
expect(room.getLiveTimeline().getEvents()).toEqual(
|
|
[eventA],
|
|
);
|
|
|
|
// the event has to have been failed or queued before it can be
|
|
// cancelled
|
|
room.updatePendingEvent(eventA, EventStatus.NOT_SENT);
|
|
|
|
let callCount = 0;
|
|
room.on("Room.localEchoUpdated",
|
|
function(event, emitRoom, oldEventId, oldStatus) {
|
|
expect(event).toEqual(eventA);
|
|
expect(event.status).toEqual(EventStatus.CANCELLED);
|
|
expect(emitRoom).toEqual(room);
|
|
expect(oldEventId).toEqual(eventId);
|
|
expect(oldStatus).toEqual(EventStatus.NOT_SENT);
|
|
callCount++;
|
|
});
|
|
|
|
room.updatePendingEvent(eventA, EventStatus.CANCELLED);
|
|
expect(room.getLiveTimeline().getEvents()).toEqual([]);
|
|
expect(callCount).toEqual(1);
|
|
});
|
|
});
|
|
});
|