1 | "use strict";
|
2 | import 'source-map-support/register';
|
3 | const sdk = require("../..");
|
4 | const EventStatus = sdk.EventStatus;
|
5 | const HttpBackend = require("matrix-mock-request");
|
6 | const utils = require("../test-utils");
|
7 |
|
8 | import Promise from 'bluebird';
|
9 | import expect from 'expect';
|
10 |
|
11 | describe("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": {
|
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 |
|
94 | NEXT_SYNC_DATA.rooms.join[roomId].state.events.push(e.__prev_event);
|
95 | }
|
96 |
|
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);
|
108 | httpBackend = new HttpBackend();
|
109 | sdk.request(httpBackend.requestFn);
|
110 | client = sdk.createClient({
|
111 | baseUrl: baseUrl,
|
112 | userId: userId,
|
113 | accessToken: accessToken,
|
114 |
|
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 |
|
148 | expect(room.timeline.length).toEqual(2);
|
149 |
|
150 | expect(room.timeline[1].status).toEqual(EventStatus.SENDING);
|
151 |
|
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 |
|
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 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | const joinMshipEvent = utils.mkMembership({
|
280 | mship: "join", user: userId, room: roomId, name: "Old Alice",
|
281 | url: null,
|
282 | });
|
283 |
|
284 |
|
285 |
|
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 |
|
297 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | });
|