You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
There is a common pattern in the tests which is, when we want to mock a /sync, to flush it, and then, in the next tick of the promise loop, to wait for the syncing event. However, this is racy: there is no guarantee that the syncing event will not happen before the next tick of the promise loop. Instead, we should set the expectation of the syncing event, then do the flush. (Technically we only need to wait for the syncing event, but by waiting for both we'll catch any errors thrown by the flush, and make sure we don't have any outstanding flushes before proceeding). Add a utility method to TestClient to do the above, and use it where we have a TestClient. (Also fixes a couple of other minor buglets in the tests).
603 lines
22 KiB
JavaScript
603 lines
22 KiB
JavaScript
"use strict";
|
|
import 'source-map-support/register';
|
|
const sdk = require("../..");
|
|
const EventStatus = sdk.EventStatus;
|
|
const HttpBackend = require("../mock-request");
|
|
const utils = require("../test-utils");
|
|
|
|
import q from 'q';
|
|
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);
|
|
done();
|
|
});
|
|
|
|
httpBackend.flush("/messages", 1);
|
|
httpBackend.flush("/sync", 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);
|
|
done();
|
|
});
|
|
|
|
httpBackend.flush("/messages", 1);
|
|
httpBackend.flush("/sync", 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]);
|
|
done();
|
|
});
|
|
|
|
httpBackend.flush("/messages", 1);
|
|
httpBackend.flush("/sync", 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("/sync", 1);
|
|
httpBackend.flush("/messages", 1).done(function() {
|
|
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 q.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 q.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 q.all([
|
|
httpBackend.flush("/sync", 1),
|
|
utils.syncPromise(client),
|
|
]).then(() => {
|
|
const room = client.getRoom(roomId);
|
|
return q.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 q.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 q.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 q.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 q.all([
|
|
httpBackend.flush("/sync", 1),
|
|
utils.syncPromise(client),
|
|
]).then(() => {
|
|
const room = client.getRoom(roomId);
|
|
return q.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 q.all([
|
|
httpBackend.flush("/sync", 1),
|
|
utils.syncPromise(client),
|
|
]).then(() => {
|
|
const room = client.getRoom(roomId);
|
|
|
|
httpBackend.flush("/messages", 1);
|
|
return q.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 q.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 q.all([
|
|
httpBackend.flush("/sync", 1),
|
|
utils.syncPromise(client),
|
|
]).then(function() {
|
|
expect(emitCount).toEqual(1);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|