matrix-js-sdk
Version:
Matrix Client-Server SDK for Javascript
614 lines (558 loc) • 23.5 kB
JavaScript
;
import 'source-map-support/register';
const sdk = require("../..");
const EventStatus = sdk.EventStatus;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
import Promise from 'bluebird';
import expect from 'expect';
describe("MatrixClient room timelines", function() {
const baseUrl = "http://localhost.or.something";
let client = null;
let httpBackend = null;
const userId = "@alice:localhost";
const userName = "Alice";
const accessToken = "aseukfgwef";
const roomId = "!foo:bar";
const otherUserId = "@bob:localhost";
const USER_MEMBERSHIP_EVENT = utils.mkMembership({
room: roomId, mship: "join", user: userId, name: userName,
});
const ROOM_NAME_EVENT = utils.mkEvent({
type: "m.room.name", room: roomId, user: otherUserId,
content: {
name: "Old room name",
},
});
let NEXT_SYNC_DATA;
const SYNC_DATA = {
next_batch: "s_5_3",
rooms: {
join: {
"!foo:bar": { // roomId
timeline: {
events: [
utils.mkMessage({
room: roomId, user: otherUserId, msg: "hello",
}),
],
prev_batch: "f_1_1",
},
state: {
events: [
ROOM_NAME_EVENT,
utils.mkMembership({
room: roomId, mship: "join",
user: otherUserId, name: "Bob",
}),
USER_MEMBERSHIP_EVENT,
utils.mkEvent({
type: "m.room.create", room: roomId, user: userId,
content: {
creator: userId,
},
}),
],
},
},
},
},
};
function setNextSyncData(events) {
events = events || [];
NEXT_SYNC_DATA = {
next_batch: "n",
presence: { events: [] },
rooms: {
invite: {},
join: {
"!foo:bar": {
timeline: { events: [] },
state: { events: [] },
ephemeral: { events: [] },
},
},
leave: {},
},
};
events.forEach(function(e) {
if (e.room_id !== roomId) {
throw new Error("setNextSyncData only works with one room id");
}
if (e.state_key) {
if (e.__prev_event === undefined) {
throw new Error(
"setNextSyncData needs the prev state set to '__prev_event' " +
"for " + e.type,
);
}
if (e.__prev_event !== null) {
// push the previous state for this event type
NEXT_SYNC_DATA.rooms.join[roomId].state.events.push(e.__prev_event);
}
// push the current
NEXT_SYNC_DATA.rooms.join[roomId].timeline.events.push(e);
} else if (["m.typing", "m.receipt"].indexOf(e.type) !== -1) {
NEXT_SYNC_DATA.rooms.join[roomId].ephemeral.events.push(e);
} else {
NEXT_SYNC_DATA.rooms.join[roomId].timeline.events.push(e);
}
});
}
beforeEach(function(done) {
utils.beforeEach(this); // eslint-disable-line no-invalid-this
httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
// these tests should work with or without timelineSupport
timelineSupport: true,
});
setNextSyncData();
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
httpBackend.when("GET", "/sync").respond(200, SYNC_DATA);
httpBackend.when("GET", "/sync").respond(200, function() {
return NEXT_SYNC_DATA;
});
client.startClient();
httpBackend.flush("/pushrules").then(function() {
return httpBackend.flush("/filter");
}).nodeify(done);
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
client.stopClient();
});
describe("local echo events", function() {
it("should be added immediately after calling MatrixClient.sendEvent " +
"with EventStatus.SENDING and the right event.sender", function(done) {
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.sendTextMessage(roomId, "I am a fish", "txn1");
// check it was added
expect(room.timeline.length).toEqual(2);
// check status
expect(room.timeline[1].status).toEqual(EventStatus.SENDING);
// check member
const member = room.timeline[1].sender;
expect(member.userId).toEqual(userId);
expect(member.name).toEqual(userName);
httpBackend.flush("/sync", 1).done(function() {
done();
});
});
httpBackend.flush("/sync", 1);
});
it("should be updated correctly when the send request finishes " +
"BEFORE the event comes down the event stream", function(done) {
const eventId = "$foo:bar";
httpBackend.when("PUT", "/txn1").respond(200, {
event_id: eventId,
});
const ev = utils.mkMessage({
body: "I am a fish", user: userId, room: roomId,
});
ev.event_id = eventId;
ev.unsigned = {transaction_id: "txn1"};
setNextSyncData([ev]);
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
client.sendTextMessage(roomId, "I am a fish", "txn1").done(
function() {
expect(room.timeline[1].getId()).toEqual(eventId);
httpBackend.flush("/sync", 1).done(function() {
expect(room.timeline[1].getId()).toEqual(eventId);
done();
});
});
httpBackend.flush("/txn1", 1);
});
httpBackend.flush("/sync", 1);
});
it("should be updated correctly when the send request finishes " +
"AFTER the event comes down the event stream", function(done) {
const eventId = "$foo:bar";
httpBackend.when("PUT", "/txn1").respond(200, {
event_id: eventId,
});
const ev = utils.mkMessage({
body: "I am a fish", user: userId, room: roomId,
});
ev.event_id = eventId;
ev.unsigned = {transaction_id: "txn1"};
setNextSyncData([ev]);
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
httpBackend.flush("/sync", 1).done(function() {
expect(room.timeline.length).toEqual(2);
httpBackend.flush("/txn1", 1);
promise.done(function() {
expect(room.timeline.length).toEqual(2);
expect(room.timeline[1].getId()).toEqual(eventId);
done();
});
});
});
httpBackend.flush("/sync", 1);
});
});
describe("paginated events", function() {
let sbEvents;
const sbEndTok = "pagin_end";
beforeEach(function() {
sbEvents = [];
httpBackend.when("GET", "/messages").respond(200, function() {
return {
chunk: sbEvents,
start: "pagin_start",
end: sbEndTok,
};
});
});
it("should set Room.oldState.paginationToken to null at the start" +
" of the timeline.", function(done) {
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
expect(room.timeline.length).toEqual(1);
expect(room.oldState.paginationToken).toBe(null);
// still have a sync to flush
httpBackend.flush("/sync", 1).then(() => {
done();
});
});
httpBackend.flush("/messages", 1);
});
httpBackend.flush("/sync", 1);
});
it("should set the right event.sender values", function(done) {
// We're aiming for an eventual timeline of:
//
// 'Old Alice' joined the room
// <Old Alice> I'm old alice
// @alice:localhost changed their name from 'Old Alice' to 'Alice'
// <Alice> I'm alice
// ------^ /messages results above this point, /sync result below
// <Bob> hello
// make an m.room.member event for alice's join
const joinMshipEvent = utils.mkMembership({
mship: "join", user: userId, room: roomId, name: "Old Alice",
url: null,
});
// make an m.room.member event with prev_content for alice's nick
// change
const oldMshipEvent = utils.mkMembership({
mship: "join", user: userId, room: roomId, name: userName,
url: "mxc://some/url",
});
oldMshipEvent.prev_content = {
displayname: "Old Alice",
avatar_url: null,
membership: "join",
};
// set the list of events to return on scrollback (/messages)
// N.B. synapse returns /messages in reverse chronological order
sbEvents = [
utils.mkMessage({
user: userId, room: roomId, msg: "I'm alice",
}),
oldMshipEvent,
utils.mkMessage({
user: userId, room: roomId, msg: "I'm old alice",
}),
joinMshipEvent,
];
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
// sync response
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
expect(room.timeline.length).toEqual(5);
const joinMsg = room.timeline[0];
expect(joinMsg.sender.name).toEqual("Old Alice");
const oldMsg = room.timeline[1];
expect(oldMsg.sender.name).toEqual("Old Alice");
const newMsg = room.timeline[3];
expect(newMsg.sender.name).toEqual(userName);
// still have a sync to flush
httpBackend.flush("/sync", 1).then(() => {
done();
});
});
httpBackend.flush("/messages", 1);
});
httpBackend.flush("/sync", 1);
});
it("should add it them to the right place in the timeline", function(done) {
// set the list of events to return on scrollback
sbEvents = [
utils.mkMessage({
user: userId, room: roomId, msg: "I am new",
}),
utils.mkMessage({
user: userId, room: roomId, msg: "I am old",
}),
];
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
expect(room.timeline.length).toEqual(1);
client.scrollback(room).done(function() {
expect(room.timeline.length).toEqual(3);
expect(room.timeline[0].event).toEqual(sbEvents[1]);
expect(room.timeline[1].event).toEqual(sbEvents[0]);
// still have a sync to flush
httpBackend.flush("/sync", 1).then(() => {
done();
});
});
httpBackend.flush("/messages", 1);
});
httpBackend.flush("/sync", 1);
});
it("should use 'end' as the next pagination token", function(done) {
// set the list of events to return on scrollback
sbEvents = [
utils.mkMessage({
user: userId, room: roomId, msg: "I am new",
}),
];
client.on("sync", function(state) {
if (state !== "PREPARED") {
return;
}
const room = client.getRoom(roomId);
expect(room.oldState.paginationToken).toBeTruthy();
client.scrollback(room, 1).done(function() {
expect(room.oldState.paginationToken).toEqual(sbEndTok);
});
httpBackend.flush("/messages", 1).done(function() {
// still have a sync to flush
httpBackend.flush("/sync", 1).then(() => {
done();
});
});
});
httpBackend.flush("/sync", 1);
});
});
describe("new events", function() {
it("should be added to the right place in the timeline", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
utils.mkMessage({user: userId, room: roomId}),
];
setNextSyncData(eventData);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
const room = client.getRoom(roomId);
let index = 0;
client.on("Room.timeline", function(event, rm, toStart) {
expect(toStart).toBe(false);
expect(rm).toEqual(room);
expect(event.event).toEqual(eventData[index]);
index += 1;
});
httpBackend.flush("/messages", 1);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(function() {
expect(index).toEqual(2);
expect(room.timeline.length).toEqual(3);
expect(room.timeline[2].event).toEqual(
eventData[1],
);
expect(room.timeline[1].event).toEqual(
eventData[0],
);
});
});
});
it("should set the right event.sender values", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
utils.mkMembership({
user: userId, room: roomId, mship: "join", name: "New Name",
}),
utils.mkMessage({user: userId, room: roomId}),
];
eventData[1].__prev_event = USER_MEMBERSHIP_EVENT;
setNextSyncData(eventData);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
const room = client.getRoom(roomId);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(function() {
const preNameEvent = room.timeline[room.timeline.length - 3];
const postNameEvent = room.timeline[room.timeline.length - 1];
expect(preNameEvent.sender.name).toEqual(userName);
expect(postNameEvent.sender.name).toEqual("New Name");
});
});
});
it("should set the right room.name", function() {
const secondRoomNameEvent = utils.mkEvent({
user: userId, room: roomId, type: "m.room.name", content: {
name: "Room 2",
},
});
secondRoomNameEvent.__prev_event = ROOM_NAME_EVENT;
setNextSyncData([secondRoomNameEvent]);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
const room = client.getRoom(roomId);
let nameEmitCount = 0;
client.on("Room.name", function(rm) {
nameEmitCount += 1;
});
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(function() {
expect(nameEmitCount).toEqual(1);
expect(room.name).toEqual("Room 2");
// do another round
const thirdRoomNameEvent = utils.mkEvent({
user: userId, room: roomId, type: "m.room.name", content: {
name: "Room 3",
},
});
thirdRoomNameEvent.__prev_event = secondRoomNameEvent;
setNextSyncData([thirdRoomNameEvent]);
httpBackend.when("GET", "/sync").respond(200, NEXT_SYNC_DATA);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]);
}).then(function() {
expect(nameEmitCount).toEqual(2);
expect(room.name).toEqual("Room 3");
});
});
});
it("should set the right room members", function() {
const userC = "@cee:bar";
const userD = "@dee:bar";
const eventData = [
utils.mkMembership({
user: userC, room: roomId, mship: "join", name: "C",
}),
utils.mkMembership({
user: userC, room: roomId, mship: "invite", skey: userD,
}),
];
eventData[0].__prev_event = null;
eventData[1].__prev_event = null;
setNextSyncData(eventData);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
const room = client.getRoom(roomId);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(function() {
expect(room.currentState.getMembers().length).toEqual(4);
expect(room.currentState.getMember(userC).name).toEqual("C");
expect(room.currentState.getMember(userC).membership).toEqual(
"join",
);
expect(room.currentState.getMember(userD).name).toEqual(userD);
expect(room.currentState.getMember(userD).membership).toEqual(
"invite",
);
});
});
});
});
describe("gappy sync", function() {
it("should copy the last known state to the new timeline", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
const room = client.getRoom(roomId);
httpBackend.flush("/messages", 1);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(function() {
expect(room.timeline.length).toEqual(1);
expect(room.timeline[0].event).toEqual(eventData[0]);
expect(room.currentState.getMembers().length).toEqual(2);
expect(room.currentState.getMember(userId).name).toEqual(userName);
expect(room.currentState.getMember(userId).membership).toEqual(
"join",
);
expect(room.currentState.getMember(otherUserId).name).toEqual("Bob");
expect(room.currentState.getMember(otherUserId).membership).toEqual(
"join",
);
});
});
});
it("should emit a 'Room.timelineReset' event", function() {
const eventData = [
utils.mkMessage({user: userId, room: roomId}),
];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(() => {
const room = client.getRoom(roomId);
let emitCount = 0;
client.on("Room.timelineReset", function(emitRoom) {
expect(emitRoom).toEqual(room);
emitCount++;
});
httpBackend.flush("/messages", 1);
return Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]).then(function() {
expect(emitCount).toEqual(1);
});
});
});
});
});