UNPKG

23.5 kBJavaScriptView Raw
1"use strict";
2import 'source-map-support/register';
3const sdk = require("../..");
4const EventStatus = sdk.EventStatus;
5const HttpBackend = require("matrix-mock-request");
6const utils = require("../test-utils");
7
8import Promise from 'bluebird';
9import expect from 'expect';
10
11describe("MatrixClient room timelines", function() {
12 const baseUrl = "http://localhost.or.something";
13 let client = null;
14 let httpBackend = null;
15 const userId = "@alice:localhost";
16 const userName = "Alice";
17 const accessToken = "aseukfgwef";
18 const roomId = "!foo:bar";
19 const otherUserId = "@bob:localhost";
20 const USER_MEMBERSHIP_EVENT = utils.mkMembership({
21 room: roomId, mship: "join", user: userId, name: userName,
22 });
23 const ROOM_NAME_EVENT = utils.mkEvent({
24 type: "m.room.name", room: roomId, user: otherUserId,
25 content: {
26 name: "Old room name",
27 },
28 });
29 let NEXT_SYNC_DATA;
30 const SYNC_DATA = {
31 next_batch: "s_5_3",
32 rooms: {
33 join: {
34 "!foo:bar": { // roomId
35 timeline: {
36 events: [
37 utils.mkMessage({
38 room: roomId, user: otherUserId, msg: "hello",
39 }),
40 ],
41 prev_batch: "f_1_1",
42 },
43 state: {
44 events: [
45 ROOM_NAME_EVENT,
46 utils.mkMembership({
47 room: roomId, mship: "join",
48 user: otherUserId, name: "Bob",
49 }),
50 USER_MEMBERSHIP_EVENT,
51 utils.mkEvent({
52 type: "m.room.create", room: roomId, user: userId,
53 content: {
54 creator: userId,
55 },
56 }),
57 ],
58 },
59 },
60 },
61 },
62 };
63
64 function setNextSyncData(events) {
65 events = events || [];
66 NEXT_SYNC_DATA = {
67 next_batch: "n",
68 presence: { events: [] },
69 rooms: {
70 invite: {},
71 join: {
72 "!foo:bar": {
73 timeline: { events: [] },
74 state: { events: [] },
75 ephemeral: { events: [] },
76 },
77 },
78 leave: {},
79 },
80 };
81 events.forEach(function(e) {
82 if (e.room_id !== roomId) {
83 throw new Error("setNextSyncData only works with one room id");
84 }
85 if (e.state_key) {
86 if (e.__prev_event === undefined) {
87 throw new Error(
88 "setNextSyncData needs the prev state set to '__prev_event' " +
89 "for " + e.type,
90 );
91 }
92 if (e.__prev_event !== null) {
93 // push the previous state for this event type
94 NEXT_SYNC_DATA.rooms.join[roomId].state.events.push(e.__prev_event);
95 }
96 // push the current
97 NEXT_SYNC_DATA.rooms.join[roomId].timeline.events.push(e);
98 } else if (["m.typing", "m.receipt"].indexOf(e.type) !== -1) {
99 NEXT_SYNC_DATA.rooms.join[roomId].ephemeral.events.push(e);
100 } else {
101 NEXT_SYNC_DATA.rooms.join[roomId].timeline.events.push(e);
102 }
103 });
104 }
105
106 beforeEach(function(done) {
107 utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
108 httpBackend = new HttpBackend();
109 sdk.request(httpBackend.requestFn);
110 client = sdk.createClient({
111 baseUrl: baseUrl,
112 userId: userId,
113 accessToken: accessToken,
114 // these tests should work with or without timelineSupport
115 timelineSupport: true,
116 });
117 setNextSyncData();
118 httpBackend.when("GET", "/pushrules").respond(200, {});
119 httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
120 httpBackend.when("GET", "/sync").respond(200, SYNC_DATA);
121 httpBackend.when("GET", "/sync").respond(200, function() {
122 return NEXT_SYNC_DATA;
123 });
124 client.startClient();
125 httpBackend.flush("/pushrules").then(function() {
126 return httpBackend.flush("/filter");
127 }).nodeify(done);
128 });
129
130 afterEach(function() {
131 httpBackend.verifyNoOutstandingExpectation();
132 client.stopClient();
133 return httpBackend.stop();
134 });
135
136 describe("local echo events", function() {
137 it("should be added immediately after calling MatrixClient.sendEvent " +
138 "with EventStatus.SENDING and the right event.sender", function(done) {
139 client.on("sync", function(state) {
140 if (state !== "PREPARED") {
141 return;
142 }
143 const room = client.getRoom(roomId);
144 expect(room.timeline.length).toEqual(1);
145
146 client.sendTextMessage(roomId, "I am a fish", "txn1");
147 // check it was added
148 expect(room.timeline.length).toEqual(2);
149 // check status
150 expect(room.timeline[1].status).toEqual(EventStatus.SENDING);
151 // check member
152 const member = room.timeline[1].sender;
153 expect(member.userId).toEqual(userId);
154 expect(member.name).toEqual(userName);
155
156 httpBackend.flush("/sync", 1).done(function() {
157 done();
158 });
159 });
160 httpBackend.flush("/sync", 1);
161 });
162
163 it("should be updated correctly when the send request finishes " +
164 "BEFORE the event comes down the event stream", function(done) {
165 const eventId = "$foo:bar";
166 httpBackend.when("PUT", "/txn1").respond(200, {
167 event_id: eventId,
168 });
169
170 const ev = utils.mkMessage({
171 body: "I am a fish", user: userId, room: roomId,
172 });
173 ev.event_id = eventId;
174 ev.unsigned = {transaction_id: "txn1"};
175 setNextSyncData([ev]);
176
177 client.on("sync", function(state) {
178 if (state !== "PREPARED") {
179 return;
180 }
181 const room = client.getRoom(roomId);
182 client.sendTextMessage(roomId, "I am a fish", "txn1").done(
183 function() {
184 expect(room.timeline[1].getId()).toEqual(eventId);
185 httpBackend.flush("/sync", 1).done(function() {
186 expect(room.timeline[1].getId()).toEqual(eventId);
187 done();
188 });
189 });
190 httpBackend.flush("/txn1", 1);
191 });
192 httpBackend.flush("/sync", 1);
193 });
194
195 it("should be updated correctly when the send request finishes " +
196 "AFTER the event comes down the event stream", function(done) {
197 const eventId = "$foo:bar";
198 httpBackend.when("PUT", "/txn1").respond(200, {
199 event_id: eventId,
200 });
201
202 const ev = utils.mkMessage({
203 body: "I am a fish", user: userId, room: roomId,
204 });
205 ev.event_id = eventId;
206 ev.unsigned = {transaction_id: "txn1"};
207 setNextSyncData([ev]);
208
209 client.on("sync", function(state) {
210 if (state !== "PREPARED") {
211 return;
212 }
213 const room = client.getRoom(roomId);
214 const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
215 httpBackend.flush("/sync", 1).done(function() {
216 expect(room.timeline.length).toEqual(2);
217 httpBackend.flush("/txn1", 1);
218 promise.done(function() {
219 expect(room.timeline.length).toEqual(2);
220 expect(room.timeline[1].getId()).toEqual(eventId);
221 done();
222 });
223 });
224 });
225 httpBackend.flush("/sync", 1);
226 });
227 });
228
229 describe("paginated events", function() {
230 let sbEvents;
231 const sbEndTok = "pagin_end";
232
233 beforeEach(function() {
234 sbEvents = [];
235 httpBackend.when("GET", "/messages").respond(200, function() {
236 return {
237 chunk: sbEvents,
238 start: "pagin_start",
239 end: sbEndTok,
240 };
241 });
242 });
243
244 it("should set Room.oldState.paginationToken to null at the start" +
245 " of the timeline.", function(done) {
246 client.on("sync", function(state) {
247 if (state !== "PREPARED") {
248 return;
249 }
250 const room = client.getRoom(roomId);
251 expect(room.timeline.length).toEqual(1);
252
253 client.scrollback(room).done(function() {
254 expect(room.timeline.length).toEqual(1);
255 expect(room.oldState.paginationToken).toBe(null);
256
257 // still have a sync to flush
258 httpBackend.flush("/sync", 1).then(() => {
259 done();
260 });
261 });
262
263 httpBackend.flush("/messages", 1);
264 });
265 httpBackend.flush("/sync", 1);
266 });
267
268 it("should set the right event.sender values", function(done) {
269 // We're aiming for an eventual timeline of:
270 //
271 // 'Old Alice' joined the room
272 // <Old Alice> I'm old alice
273 // @alice:localhost changed their name from 'Old Alice' to 'Alice'
274 // <Alice> I'm alice
275 // ------^ /messages results above this point, /sync result below
276 // <Bob> hello
277
278 // make an m.room.member event for alice's join
279 const joinMshipEvent = utils.mkMembership({
280 mship: "join", user: userId, room: roomId, name: "Old Alice",
281 url: null,
282 });
283
284 // make an m.room.member event with prev_content for alice's nick
285 // change
286 const oldMshipEvent = utils.mkMembership({
287 mship: "join", user: userId, room: roomId, name: userName,
288 url: "mxc://some/url",
289 });
290 oldMshipEvent.prev_content = {
291 displayname: "Old Alice",
292 avatar_url: null,
293 membership: "join",
294 };
295
296 // set the list of events to return on scrollback (/messages)
297 // N.B. synapse returns /messages in reverse chronological order
298 sbEvents = [
299 utils.mkMessage({
300 user: userId, room: roomId, msg: "I'm alice",
301 }),
302 oldMshipEvent,
303 utils.mkMessage({
304 user: userId, room: roomId, msg: "I'm old alice",
305 }),
306 joinMshipEvent,
307 ];
308
309 client.on("sync", function(state) {
310 if (state !== "PREPARED") {
311 return;
312 }
313 const room = client.getRoom(roomId);
314 // sync response
315 expect(room.timeline.length).toEqual(1);
316
317 client.scrollback(room).done(function() {
318 expect(room.timeline.length).toEqual(5);
319 const joinMsg = room.timeline[0];
320 expect(joinMsg.sender.name).toEqual("Old Alice");
321 const oldMsg = room.timeline[1];
322 expect(oldMsg.sender.name).toEqual("Old Alice");
323 const newMsg = room.timeline[3];
324 expect(newMsg.sender.name).toEqual(userName);
325
326 // still have a sync to flush
327 httpBackend.flush("/sync", 1).then(() => {
328 done();
329 });
330 });
331
332 httpBackend.flush("/messages", 1);
333 });
334 httpBackend.flush("/sync", 1);
335 });
336
337 it("should add it them to the right place in the timeline", function(done) {
338 // set the list of events to return on scrollback
339 sbEvents = [
340 utils.mkMessage({
341 user: userId, room: roomId, msg: "I am new",
342 }),
343 utils.mkMessage({
344 user: userId, room: roomId, msg: "I am old",
345 }),
346 ];
347
348 client.on("sync", function(state) {
349 if (state !== "PREPARED") {
350 return;
351 }
352 const room = client.getRoom(roomId);
353 expect(room.timeline.length).toEqual(1);
354
355 client.scrollback(room).done(function() {
356 expect(room.timeline.length).toEqual(3);
357 expect(room.timeline[0].event).toEqual(sbEvents[1]);
358 expect(room.timeline[1].event).toEqual(sbEvents[0]);
359
360 // still have a sync to flush
361 httpBackend.flush("/sync", 1).then(() => {
362 done();
363 });
364 });
365
366 httpBackend.flush("/messages", 1);
367 });
368 httpBackend.flush("/sync", 1);
369 });
370
371 it("should use 'end' as the next pagination token", function(done) {
372 // set the list of events to return on scrollback
373 sbEvents = [
374 utils.mkMessage({
375 user: userId, room: roomId, msg: "I am new",
376 }),
377 ];
378
379 client.on("sync", function(state) {
380 if (state !== "PREPARED") {
381 return;
382 }
383 const room = client.getRoom(roomId);
384 expect(room.oldState.paginationToken).toBeTruthy();
385
386 client.scrollback(room, 1).done(function() {
387 expect(room.oldState.paginationToken).toEqual(sbEndTok);
388 });
389
390 httpBackend.flush("/messages", 1).done(function() {
391 // still have a sync to flush
392 httpBackend.flush("/sync", 1).then(() => {
393 done();
394 });
395 });
396 });
397 httpBackend.flush("/sync", 1);
398 });
399 });
400
401 describe("new events", function() {
402 it("should be added to the right place in the timeline", function() {
403 const eventData = [
404 utils.mkMessage({user: userId, room: roomId}),
405 utils.mkMessage({user: userId, room: roomId}),
406 ];
407 setNextSyncData(eventData);
408
409 return Promise.all([
410 httpBackend.flush("/sync", 1),
411 utils.syncPromise(client),
412 ]).then(() => {
413 const room = client.getRoom(roomId);
414
415 let index = 0;
416 client.on("Room.timeline", function(event, rm, toStart) {
417 expect(toStart).toBe(false);
418 expect(rm).toEqual(room);
419 expect(event.event).toEqual(eventData[index]);
420 index += 1;
421 });
422
423 httpBackend.flush("/messages", 1);
424 return Promise.all([
425 httpBackend.flush("/sync", 1),
426 utils.syncPromise(client),
427 ]).then(function() {
428 expect(index).toEqual(2);
429 expect(room.timeline.length).toEqual(3);
430 expect(room.timeline[2].event).toEqual(
431 eventData[1],
432 );
433 expect(room.timeline[1].event).toEqual(
434 eventData[0],
435 );
436 });
437 });
438 });
439
440 it("should set the right event.sender values", function() {
441 const eventData = [
442 utils.mkMessage({user: userId, room: roomId}),
443 utils.mkMembership({
444 user: userId, room: roomId, mship: "join", name: "New Name",
445 }),
446 utils.mkMessage({user: userId, room: roomId}),
447 ];
448 eventData[1].__prev_event = USER_MEMBERSHIP_EVENT;
449 setNextSyncData(eventData);
450
451 return Promise.all([
452 httpBackend.flush("/sync", 1),
453 utils.syncPromise(client),
454 ]).then(() => {
455 const room = client.getRoom(roomId);
456 return Promise.all([
457 httpBackend.flush("/sync", 1),
458 utils.syncPromise(client),
459 ]).then(function() {
460 const preNameEvent = room.timeline[room.timeline.length - 3];
461 const postNameEvent = room.timeline[room.timeline.length - 1];
462 expect(preNameEvent.sender.name).toEqual(userName);
463 expect(postNameEvent.sender.name).toEqual("New Name");
464 });
465 });
466 });
467
468 it("should set the right room.name", function() {
469 const secondRoomNameEvent = utils.mkEvent({
470 user: userId, room: roomId, type: "m.room.name", content: {
471 name: "Room 2",
472 },
473 });
474 secondRoomNameEvent.__prev_event = ROOM_NAME_EVENT;
475 setNextSyncData([secondRoomNameEvent]);
476
477 return Promise.all([
478 httpBackend.flush("/sync", 1),
479 utils.syncPromise(client),
480 ]).then(() => {
481 const room = client.getRoom(roomId);
482 let nameEmitCount = 0;
483 client.on("Room.name", function(rm) {
484 nameEmitCount += 1;
485 });
486
487 return Promise.all([
488 httpBackend.flush("/sync", 1),
489 utils.syncPromise(client),
490 ]).then(function() {
491 expect(nameEmitCount).toEqual(1);
492 expect(room.name).toEqual("Room 2");
493 // do another round
494 const thirdRoomNameEvent = utils.mkEvent({
495 user: userId, room: roomId, type: "m.room.name", content: {
496 name: "Room 3",
497 },
498 });
499 thirdRoomNameEvent.__prev_event = secondRoomNameEvent;
500 setNextSyncData([thirdRoomNameEvent]);
501 httpBackend.when("GET", "/sync").respond(200, NEXT_SYNC_DATA);
502 return Promise.all([
503 httpBackend.flush("/sync", 1),
504 utils.syncPromise(client),
505 ]);
506 }).then(function() {
507 expect(nameEmitCount).toEqual(2);
508 expect(room.name).toEqual("Room 3");
509 });
510 });
511 });
512
513 it("should set the right room members", function() {
514 const userC = "@cee:bar";
515 const userD = "@dee:bar";
516 const eventData = [
517 utils.mkMembership({
518 user: userC, room: roomId, mship: "join", name: "C",
519 }),
520 utils.mkMembership({
521 user: userC, room: roomId, mship: "invite", skey: userD,
522 }),
523 ];
524 eventData[0].__prev_event = null;
525 eventData[1].__prev_event = null;
526 setNextSyncData(eventData);
527
528 return Promise.all([
529 httpBackend.flush("/sync", 1),
530 utils.syncPromise(client),
531 ]).then(() => {
532 const room = client.getRoom(roomId);
533 return Promise.all([
534 httpBackend.flush("/sync", 1),
535 utils.syncPromise(client),
536 ]).then(function() {
537 expect(room.currentState.getMembers().length).toEqual(4);
538 expect(room.currentState.getMember(userC).name).toEqual("C");
539 expect(room.currentState.getMember(userC).membership).toEqual(
540 "join",
541 );
542 expect(room.currentState.getMember(userD).name).toEqual(userD);
543 expect(room.currentState.getMember(userD).membership).toEqual(
544 "invite",
545 );
546 });
547 });
548 });
549 });
550
551 describe("gappy sync", function() {
552 it("should copy the last known state to the new timeline", function() {
553 const eventData = [
554 utils.mkMessage({user: userId, room: roomId}),
555 ];
556 setNextSyncData(eventData);
557 NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
558
559 return Promise.all([
560 httpBackend.flush("/sync", 1),
561 utils.syncPromise(client),
562 ]).then(() => {
563 const room = client.getRoom(roomId);
564
565 httpBackend.flush("/messages", 1);
566 return Promise.all([
567 httpBackend.flush("/sync", 1),
568 utils.syncPromise(client),
569 ]).then(function() {
570 expect(room.timeline.length).toEqual(1);
571 expect(room.timeline[0].event).toEqual(eventData[0]);
572 expect(room.currentState.getMembers().length).toEqual(2);
573 expect(room.currentState.getMember(userId).name).toEqual(userName);
574 expect(room.currentState.getMember(userId).membership).toEqual(
575 "join",
576 );
577 expect(room.currentState.getMember(otherUserId).name).toEqual("Bob");
578 expect(room.currentState.getMember(otherUserId).membership).toEqual(
579 "join",
580 );
581 });
582 });
583 });
584
585 it("should emit a 'Room.timelineReset' event", function() {
586 const eventData = [
587 utils.mkMessage({user: userId, room: roomId}),
588 ];
589 setNextSyncData(eventData);
590 NEXT_SYNC_DATA.rooms.join[roomId].timeline.limited = true;
591
592 return Promise.all([
593 httpBackend.flush("/sync", 1),
594 utils.syncPromise(client),
595 ]).then(() => {
596 const room = client.getRoom(roomId);
597
598 let emitCount = 0;
599 client.on("Room.timelineReset", function(emitRoom) {
600 expect(emitRoom).toEqual(room);
601 emitCount++;
602 });
603
604 httpBackend.flush("/messages", 1);
605 return Promise.all([
606 httpBackend.flush("/sync", 1),
607 utils.syncPromise(client),
608 ]).then(function() {
609 expect(emitCount).toEqual(1);
610 });
611 });
612 });
613 });
614});