UNPKG

29 kBJavaScriptView Raw
1"use strict";
2import 'source-map-support/register';
3import Promise from 'bluebird';
4const sdk = require("../..");
5const HttpBackend = require("matrix-mock-request");
6const utils = require("../test-utils");
7const EventTimeline = sdk.EventTimeline;
8import logger from '../../src/logger';
9
10const baseUrl = "http://localhost.or.something";
11const userId = "@alice:localhost";
12const userName = "Alice";
13const accessToken = "aseukfgwef";
14const roomId = "!foo:bar";
15const otherUserId = "@bob:localhost";
16
17const USER_MEMBERSHIP_EVENT = utils.mkMembership({
18 room: roomId, mship: "join", user: userId, name: userName,
19});
20
21const ROOM_NAME_EVENT = utils.mkEvent({
22 type: "m.room.name", room: roomId, user: otherUserId,
23 content: {
24 name: "Old room name",
25 },
26});
27
28const INITIAL_SYNC_DATA = {
29 next_batch: "s_5_3",
30 rooms: {
31 join: {
32 "!foo:bar": { // roomId
33 timeline: {
34 events: [
35 utils.mkMessage({
36 room: roomId, user: otherUserId, msg: "hello",
37 }),
38 ],
39 prev_batch: "f_1_1",
40 },
41 state: {
42 events: [
43 ROOM_NAME_EVENT,
44 utils.mkMembership({
45 room: roomId, mship: "join",
46 user: otherUserId, name: "Bob",
47 }),
48 USER_MEMBERSHIP_EVENT,
49 utils.mkEvent({
50 type: "m.room.create", room: roomId, user: userId,
51 content: {
52 creator: userId,
53 },
54 }),
55 ],
56 },
57 },
58 },
59 },
60};
61
62const EVENTS = [
63 utils.mkMessage({
64 room: roomId, user: userId, msg: "we",
65 }),
66 utils.mkMessage({
67 room: roomId, user: userId, msg: "could",
68 }),
69 utils.mkMessage({
70 room: roomId, user: userId, msg: "be",
71 }),
72 utils.mkMessage({
73 room: roomId, user: userId, msg: "heroes",
74 }),
75];
76
77// start the client, and wait for it to initialise
78function startClient(httpBackend, client) {
79 httpBackend.when("GET", "/pushrules").respond(200, {});
80 httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
81 httpBackend.when("GET", "/sync").respond(200, INITIAL_SYNC_DATA);
82
83 client.startClient();
84
85 // set up a promise which will resolve once the client is initialised
86 const deferred = Promise.defer();
87 client.on("sync", function(state) {
88 logger.log("sync", state);
89 if (state != "SYNCING") {
90 return;
91 }
92 deferred.resolve();
93 });
94
95 return Promise.all([
96 httpBackend.flushAllExpected(),
97 deferred.promise,
98 ]);
99}
100
101describe("getEventTimeline support", function() {
102 let httpBackend;
103 let client;
104
105 beforeEach(function() {
106 utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
107 httpBackend = new HttpBackend();
108 sdk.request(httpBackend.requestFn);
109 });
110
111 afterEach(function() {
112 if (client) {
113 client.stopClient();
114 }
115 return httpBackend.stop();
116 });
117
118 it("timeline support must be enabled to work", function(done) {
119 client = sdk.createClient({
120 baseUrl: baseUrl,
121 userId: userId,
122 accessToken: accessToken,
123 });
124
125 startClient(httpBackend, client,
126 ).then(function() {
127 const room = client.getRoom(roomId);
128 const timelineSet = room.getTimelineSets()[0];
129 expect(function() {
130 client.getEventTimeline(timelineSet, "event");
131 }).toThrow();
132 }).nodeify(done);
133 });
134
135 it("timeline support works when enabled", function() {
136 client = sdk.createClient({
137 baseUrl: baseUrl,
138 userId: userId,
139 accessToken: accessToken,
140 timelineSupport: true,
141 });
142
143 return startClient(httpBackend, client).then(() => {
144 const room = client.getRoom(roomId);
145 const timelineSet = room.getTimelineSets()[0];
146 expect(function() {
147 client.getEventTimeline(timelineSet, "event");
148 }).toNotThrow();
149 });
150 });
151
152
153 it("scrollback should be able to scroll back to before a gappy /sync",
154 function(done) {
155 // need a client with timelineSupport disabled to make this work
156 client = sdk.createClient({
157 baseUrl: baseUrl,
158 userId: userId,
159 accessToken: accessToken,
160 });
161 let room;
162
163 startClient(httpBackend, client,
164 ).then(function() {
165 room = client.getRoom(roomId);
166
167 httpBackend.when("GET", "/sync").respond(200, {
168 next_batch: "s_5_4",
169 rooms: {
170 join: {
171 "!foo:bar": {
172 timeline: {
173 events: [
174 EVENTS[0],
175 ],
176 prev_batch: "f_1_1",
177 },
178 },
179 },
180 },
181 });
182
183 httpBackend.when("GET", "/sync").respond(200, {
184 next_batch: "s_5_5",
185 rooms: {
186 join: {
187 "!foo:bar": {
188 timeline: {
189 events: [
190 EVENTS[1],
191 ],
192 limited: true,
193 prev_batch: "f_1_2",
194 },
195 },
196 },
197 },
198 });
199
200 return Promise.all([
201 httpBackend.flushAllExpected(),
202 utils.syncPromise(client, 2),
203 ]);
204 }).then(function() {
205 expect(room.timeline.length).toEqual(1);
206 expect(room.timeline[0].event).toEqual(EVENTS[1]);
207
208 httpBackend.when("GET", "/messages").respond(200, {
209 chunk: [EVENTS[0]],
210 start: "pagin_start",
211 end: "pagin_end",
212 });
213 httpBackend.flush("/messages", 1);
214 return client.scrollback(room);
215 }).then(function() {
216 expect(room.timeline.length).toEqual(2);
217 expect(room.timeline[0].event).toEqual(EVENTS[0]);
218 expect(room.timeline[1].event).toEqual(EVENTS[1]);
219 expect(room.oldState.paginationToken).toEqual("pagin_end");
220 }).nodeify(done);
221 });
222});
223
224import expect from 'expect';
225
226describe("MatrixClient event timelines", function() {
227 let client = null;
228 let httpBackend = null;
229
230 beforeEach(function() {
231 utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
232 httpBackend = new HttpBackend();
233 sdk.request(httpBackend.requestFn);
234
235 client = sdk.createClient({
236 baseUrl: baseUrl,
237 userId: userId,
238 accessToken: accessToken,
239 timelineSupport: true,
240 });
241
242 return startClient(httpBackend, client);
243 });
244
245 afterEach(function() {
246 httpBackend.verifyNoOutstandingExpectation();
247 client.stopClient();
248 });
249
250 describe("getEventTimeline", function() {
251 it("should create a new timeline for new events", function() {
252 const room = client.getRoom(roomId);
253 const timelineSet = room.getTimelineSets()[0];
254 httpBackend.when("GET", "/rooms/!foo%3Abar/context/event1%3Abar")
255 .respond(200, function() {
256 return {
257 start: "start_token",
258 events_before: [EVENTS[1], EVENTS[0]],
259 event: EVENTS[2],
260 events_after: [EVENTS[3]],
261 state: [
262 ROOM_NAME_EVENT,
263 USER_MEMBERSHIP_EVENT,
264 ],
265 end: "end_token",
266 };
267 });
268
269 return Promise.all([
270 client.getEventTimeline(timelineSet, "event1:bar").then(function(tl) {
271 expect(tl.getEvents().length).toEqual(4);
272 for (let i = 0; i < 4; i++) {
273 expect(tl.getEvents()[i].event).toEqual(EVENTS[i]);
274 expect(tl.getEvents()[i].sender.name).toEqual(userName);
275 }
276 expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
277 .toEqual("start_token");
278 expect(tl.getPaginationToken(EventTimeline.FORWARDS))
279 .toEqual("end_token");
280 }),
281 httpBackend.flushAllExpected(),
282 ]);
283 });
284
285 it("should return existing timeline for known events", function() {
286 const room = client.getRoom(roomId);
287 const timelineSet = room.getTimelineSets()[0];
288 httpBackend.when("GET", "/sync").respond(200, {
289 next_batch: "s_5_4",
290 rooms: {
291 join: {
292 "!foo:bar": {
293 timeline: {
294 events: [
295 EVENTS[0],
296 ],
297 prev_batch: "f_1_2",
298 },
299 },
300 },
301 },
302 });
303
304 return Promise.all([
305 httpBackend.flush("/sync"),
306 utils.syncPromise(client),
307 ]).then(function() {
308 return client.getEventTimeline(timelineSet, EVENTS[0].event_id);
309 }).then(function(tl) {
310 expect(tl.getEvents().length).toEqual(2);
311 expect(tl.getEvents()[1].event).toEqual(EVENTS[0]);
312 expect(tl.getEvents()[1].sender.name).toEqual(userName);
313 expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
314 .toEqual("f_1_1");
315 // expect(tl.getPaginationToken(EventTimeline.FORWARDS))
316 // .toEqual("s_5_4");
317 });
318 });
319
320 it("should update timelines where they overlap a previous /sync", function() {
321 const room = client.getRoom(roomId);
322 const timelineSet = room.getTimelineSets()[0];
323 httpBackend.when("GET", "/sync").respond(200, {
324 next_batch: "s_5_4",
325 rooms: {
326 join: {
327 "!foo:bar": {
328 timeline: {
329 events: [
330 EVENTS[3],
331 ],
332 prev_batch: "f_1_2",
333 },
334 },
335 },
336 },
337 });
338
339 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
340 encodeURIComponent(EVENTS[2].event_id))
341 .respond(200, function() {
342 return {
343 start: "start_token",
344 events_before: [EVENTS[1]],
345 event: EVENTS[2],
346 events_after: [EVENTS[3]],
347 end: "end_token",
348 state: [],
349 };
350 });
351
352 const deferred = Promise.defer();
353 client.on("sync", function() {
354 client.getEventTimeline(timelineSet, EVENTS[2].event_id,
355 ).then(function(tl) {
356 expect(tl.getEvents().length).toEqual(4);
357 expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
358 expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
359 expect(tl.getEvents()[3].event).toEqual(EVENTS[3]);
360 expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
361 .toEqual("start_token");
362 // expect(tl.getPaginationToken(EventTimeline.FORWARDS))
363 // .toEqual("s_5_4");
364 }).done(() => deferred.resolve(),
365 (e) => deferred.reject(e));
366 });
367
368 return Promise.all([
369 httpBackend.flushAllExpected(),
370 deferred.promise,
371 ]);
372 });
373
374 it("should join timelines where they overlap a previous /context",
375 function() {
376 const room = client.getRoom(roomId);
377 const timelineSet = room.getTimelineSets()[0];
378
379 // we fetch event 0, then 2, then 3, and finally 1. 1 is returned
380 // with context which joins them all up.
381 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
382 encodeURIComponent(EVENTS[0].event_id))
383 .respond(200, function() {
384 return {
385 start: "start_token0",
386 events_before: [],
387 event: EVENTS[0],
388 events_after: [],
389 end: "end_token0",
390 state: [],
391 };
392 });
393
394 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
395 encodeURIComponent(EVENTS[2].event_id))
396 .respond(200, function() {
397 return {
398 start: "start_token2",
399 events_before: [],
400 event: EVENTS[2],
401 events_after: [],
402 end: "end_token2",
403 state: [],
404 };
405 });
406
407 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
408 encodeURIComponent(EVENTS[3].event_id))
409 .respond(200, function() {
410 return {
411 start: "start_token3",
412 events_before: [],
413 event: EVENTS[3],
414 events_after: [],
415 end: "end_token3",
416 state: [],
417 };
418 });
419
420 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
421 encodeURIComponent(EVENTS[1].event_id))
422 .respond(200, function() {
423 return {
424 start: "start_token4",
425 events_before: [EVENTS[0]],
426 event: EVENTS[1],
427 events_after: [EVENTS[2], EVENTS[3]],
428 end: "end_token4",
429 state: [],
430 };
431 });
432
433 let tl0;
434 let tl3;
435 return Promise.all([
436 client.getEventTimeline(timelineSet, EVENTS[0].event_id,
437 ).then(function(tl) {
438 expect(tl.getEvents().length).toEqual(1);
439 tl0 = tl;
440 return client.getEventTimeline(timelineSet, EVENTS[2].event_id);
441 }).then(function(tl) {
442 expect(tl.getEvents().length).toEqual(1);
443 return client.getEventTimeline(timelineSet, EVENTS[3].event_id);
444 }).then(function(tl) {
445 expect(tl.getEvents().length).toEqual(1);
446 tl3 = tl;
447 return client.getEventTimeline(timelineSet, EVENTS[1].event_id);
448 }).then(function(tl) {
449 // we expect it to get merged in with event 2
450 expect(tl.getEvents().length).toEqual(2);
451 expect(tl.getEvents()[0].event).toEqual(EVENTS[1]);
452 expect(tl.getEvents()[1].event).toEqual(EVENTS[2]);
453 expect(tl.getNeighbouringTimeline(EventTimeline.BACKWARDS))
454 .toBe(tl0);
455 expect(tl.getNeighbouringTimeline(EventTimeline.FORWARDS))
456 .toBe(tl3);
457 expect(tl0.getPaginationToken(EventTimeline.BACKWARDS))
458 .toEqual("start_token0");
459 expect(tl0.getPaginationToken(EventTimeline.FORWARDS))
460 .toBe(null);
461 expect(tl3.getPaginationToken(EventTimeline.BACKWARDS))
462 .toBe(null);
463 expect(tl3.getPaginationToken(EventTimeline.FORWARDS))
464 .toEqual("end_token3");
465 }),
466 httpBackend.flushAllExpected(),
467 ]);
468 });
469
470 it("should fail gracefully if there is no event field", function() {
471 const room = client.getRoom(roomId);
472 const timelineSet = room.getTimelineSets()[0];
473 // we fetch event 0, then 2, then 3, and finally 1. 1 is returned
474 // with context which joins them all up.
475 httpBackend.when("GET", "/rooms/!foo%3Abar/context/event1")
476 .respond(200, function() {
477 return {
478 start: "start_token",
479 events_before: [],
480 events_after: [],
481 end: "end_token",
482 state: [],
483 };
484 });
485
486 return Promise.all([
487 client.getEventTimeline(timelineSet, "event1",
488 ).then(function(tl) {
489 // could do with a fail()
490 expect(true).toBeFalsy();
491 }, function(e) {
492 expect(String(e)).toMatch(/'event'/);
493 }),
494 httpBackend.flushAllExpected(),
495 ]);
496 });
497 });
498
499 describe("paginateEventTimeline", function() {
500 it("should allow you to paginate backwards", function() {
501 const room = client.getRoom(roomId);
502 const timelineSet = room.getTimelineSets()[0];
503
504 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
505 encodeURIComponent(EVENTS[0].event_id))
506 .respond(200, function() {
507 return {
508 start: "start_token0",
509 events_before: [],
510 event: EVENTS[0],
511 events_after: [],
512 end: "end_token0",
513 state: [],
514 };
515 });
516
517 httpBackend.when("GET", "/rooms/!foo%3Abar/messages")
518 .check(function(req) {
519 const params = req.queryParams;
520 expect(params.dir).toEqual("b");
521 expect(params.from).toEqual("start_token0");
522 expect(params.limit).toEqual(30);
523 }).respond(200, function() {
524 return {
525 chunk: [EVENTS[1], EVENTS[2]],
526 end: "start_token1",
527 };
528 });
529
530 let tl;
531 return Promise.all([
532 client.getEventTimeline(timelineSet, EVENTS[0].event_id,
533 ).then(function(tl0) {
534 tl = tl0;
535 return client.paginateEventTimeline(tl, {backwards: true});
536 }).then(function(success) {
537 expect(success).toBeTruthy();
538 expect(tl.getEvents().length).toEqual(3);
539 expect(tl.getEvents()[0].event).toEqual(EVENTS[2]);
540 expect(tl.getEvents()[1].event).toEqual(EVENTS[1]);
541 expect(tl.getEvents()[2].event).toEqual(EVENTS[0]);
542 expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
543 .toEqual("start_token1");
544 expect(tl.getPaginationToken(EventTimeline.FORWARDS))
545 .toEqual("end_token0");
546 }),
547 httpBackend.flushAllExpected(),
548 ]);
549 });
550
551
552 it("should allow you to paginate forwards", function() {
553 const room = client.getRoom(roomId);
554 const timelineSet = room.getTimelineSets()[0];
555
556 httpBackend.when("GET", "/rooms/!foo%3Abar/context/" +
557 encodeURIComponent(EVENTS[0].event_id))
558 .respond(200, function() {
559 return {
560 start: "start_token0",
561 events_before: [],
562 event: EVENTS[0],
563 events_after: [],
564 end: "end_token0",
565 state: [],
566 };
567 });
568
569 httpBackend.when("GET", "/rooms/!foo%3Abar/messages")
570 .check(function(req) {
571 const params = req.queryParams;
572 expect(params.dir).toEqual("f");
573 expect(params.from).toEqual("end_token0");
574 expect(params.limit).toEqual(20);
575 }).respond(200, function() {
576 return {
577 chunk: [EVENTS[1], EVENTS[2]],
578 end: "end_token1",
579 };
580 });
581
582 let tl;
583 return Promise.all([
584 client.getEventTimeline(timelineSet, EVENTS[0].event_id,
585 ).then(function(tl0) {
586 tl = tl0;
587 return client.paginateEventTimeline(
588 tl, {backwards: false, limit: 20});
589 }).then(function(success) {
590 expect(success).toBeTruthy();
591 expect(tl.getEvents().length).toEqual(3);
592 expect(tl.getEvents()[0].event).toEqual(EVENTS[0]);
593 expect(tl.getEvents()[1].event).toEqual(EVENTS[1]);
594 expect(tl.getEvents()[2].event).toEqual(EVENTS[2]);
595 expect(tl.getPaginationToken(EventTimeline.BACKWARDS))
596 .toEqual("start_token0");
597 expect(tl.getPaginationToken(EventTimeline.FORWARDS))
598 .toEqual("end_token1");
599 }),
600 httpBackend.flushAllExpected(),
601 ]);
602 });
603 });
604
605 describe("event timeline for sent events", function() {
606 const TXN_ID = "txn1";
607 const event = utils.mkMessage({
608 room: roomId, user: userId, msg: "a body",
609 });
610 event.unsigned = {transaction_id: TXN_ID};
611
612 beforeEach(function() {
613 // set up handlers for both the message send, and the
614 // /sync
615 httpBackend.when("PUT", "/send/m.room.message/" + TXN_ID)
616 .respond(200, {
617 event_id: event.event_id,
618 });
619 httpBackend.when("GET", "/sync").respond(200, {
620 next_batch: "s_5_4",
621 rooms: {
622 join: {
623 "!foo:bar": {
624 timeline: {
625 events: [
626 event,
627 ],
628 prev_batch: "f_1_1",
629 },
630 },
631 },
632 },
633 });
634 });
635
636 it("should work when /send returns before /sync", function() {
637 const room = client.getRoom(roomId);
638 const timelineSet = room.getTimelineSets()[0];
639
640 return Promise.all([
641 client.sendTextMessage(roomId, "a body", TXN_ID).then(function(res) {
642 expect(res.event_id).toEqual(event.event_id);
643 return client.getEventTimeline(timelineSet, event.event_id);
644 }).then(function(tl) {
645 // 2 because the initial sync contained an event
646 expect(tl.getEvents().length).toEqual(2);
647 expect(tl.getEvents()[1].getContent().body).toEqual("a body");
648
649 // now let the sync complete, and check it again
650 return Promise.all([
651 httpBackend.flush("/sync", 1),
652 utils.syncPromise(client),
653 ]);
654 }).then(function() {
655 return client.getEventTimeline(timelineSet, event.event_id);
656 }).then(function(tl) {
657 expect(tl.getEvents().length).toEqual(2);
658 expect(tl.getEvents()[1].event).toEqual(event);
659 }),
660
661 httpBackend.flush("/send/m.room.message/" + TXN_ID, 1),
662 ]);
663 });
664
665 it("should work when /send returns after /sync", function() {
666 const room = client.getRoom(roomId);
667 const timelineSet = room.getTimelineSets()[0];
668
669 return Promise.all([
670 // initiate the send, and set up checks to be done when it completes
671 // - but note that it won't complete until after the /sync does, below.
672 client.sendTextMessage(roomId, "a body", TXN_ID).then(function(res) {
673 logger.log("sendTextMessage completed");
674 expect(res.event_id).toEqual(event.event_id);
675 return client.getEventTimeline(timelineSet, event.event_id);
676 }).then(function(tl) {
677 logger.log("getEventTimeline completed (2)");
678 expect(tl.getEvents().length).toEqual(2);
679 expect(tl.getEvents()[1].getContent().body).toEqual("a body");
680 }),
681
682 Promise.all([
683 httpBackend.flush("/sync", 1),
684 utils.syncPromise(client),
685 ]).then(function() {
686 return client.getEventTimeline(timelineSet, event.event_id);
687 }).then(function(tl) {
688 logger.log("getEventTimeline completed (1)");
689 expect(tl.getEvents().length).toEqual(2);
690 expect(tl.getEvents()[1].event).toEqual(event);
691
692 // now let the send complete.
693 return httpBackend.flush("/send/m.room.message/" + TXN_ID, 1);
694 }),
695 ]);
696 });
697 });
698
699
700 it("should handle gappy syncs after redactions", function(done) {
701 // https://github.com/vector-im/vector-web/issues/1389
702
703 // a state event, followed by a redaction thereof
704 const event = utils.mkMembership({
705 room: roomId, mship: "join", user: otherUserId,
706 });
707 const redaction = utils.mkEvent({
708 type: "m.room.redaction",
709 room_id: roomId,
710 sender: otherUserId,
711 content: {},
712 });
713 redaction.redacts = event.event_id;
714
715 const syncData = {
716 next_batch: "batch1",
717 rooms: {
718 join: {},
719 },
720 };
721 syncData.rooms.join[roomId] = {
722 timeline: {
723 events: [
724 event,
725 redaction,
726 ],
727 limited: false,
728 },
729 };
730 httpBackend.when("GET", "/sync").respond(200, syncData);
731
732 Promise.all([
733 httpBackend.flushAllExpected(),
734 utils.syncPromise(client),
735 ]).then(function() {
736 const room = client.getRoom(roomId);
737 const tl = room.getLiveTimeline();
738 expect(tl.getEvents().length).toEqual(3);
739 expect(tl.getEvents()[1].isRedacted()).toBe(true);
740
741 const sync2 = {
742 next_batch: "batch2",
743 rooms: {
744 join: {},
745 },
746 };
747 sync2.rooms.join[roomId] = {
748 timeline: {
749 events: [
750 utils.mkMessage({
751 room: roomId, user: otherUserId, msg: "world",
752 }),
753 ],
754 limited: true,
755 prev_batch: "newerTok",
756 },
757 };
758 httpBackend.when("GET", "/sync").respond(200, sync2);
759
760 return Promise.all([
761 httpBackend.flushAllExpected(),
762 utils.syncPromise(client),
763 ]);
764 }).then(function() {
765 const room = client.getRoom(roomId);
766 const tl = room.getLiveTimeline();
767 expect(tl.getEvents().length).toEqual(1);
768 }).nodeify(done);
769 });
770});