UNPKG

26.2 kBJavaScriptView Raw
1"use strict";
2import 'source-map-support/register';
3const sdk = require("../..");
4const HttpBackend = require("matrix-mock-request");
5const utils = require("../test-utils");
6const MatrixEvent = sdk.MatrixEvent;
7const EventTimeline = sdk.EventTimeline;
8
9import expect from 'expect';
10import Promise from 'bluebird';
11
12describe("MatrixClient syncing", function() {
13 const baseUrl = "http://localhost.or.something";
14 let client = null;
15 let httpBackend = null;
16 const selfUserId = "@alice:localhost";
17 const selfAccessToken = "aseukfgwef";
18 const otherUserId = "@bob:localhost";
19 const userA = "@alice:bar";
20 const userB = "@bob:bar";
21 const userC = "@claire:bar";
22 const roomOne = "!foo:localhost";
23 const roomTwo = "!bar:localhost";
24
25 beforeEach(function() {
26 utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
27 httpBackend = new HttpBackend();
28 sdk.request(httpBackend.requestFn);
29 client = sdk.createClient({
30 baseUrl: baseUrl,
31 userId: selfUserId,
32 accessToken: selfAccessToken,
33 });
34 httpBackend.when("GET", "/pushrules").respond(200, {});
35 httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
36 });
37
38 afterEach(function() {
39 httpBackend.verifyNoOutstandingExpectation();
40 client.stopClient();
41 return httpBackend.stop();
42 });
43
44 describe("startClient", function() {
45 const syncData = {
46 next_batch: "batch_token",
47 rooms: {},
48 presence: {},
49 };
50
51 it("should /sync after /pushrules and /filter.", function(done) {
52 httpBackend.when("GET", "/sync").respond(200, syncData);
53
54 client.startClient();
55
56 httpBackend.flushAllExpected().done(function() {
57 done();
58 });
59 });
60
61 it("should pass the 'next_batch' token from /sync to the since= param " +
62 " of the next /sync", function(done) {
63 httpBackend.when("GET", "/sync").respond(200, syncData);
64 httpBackend.when("GET", "/sync").check(function(req) {
65 expect(req.queryParams.since).toEqual(syncData.next_batch);
66 }).respond(200, syncData);
67
68 client.startClient();
69
70 httpBackend.flushAllExpected().done(function() {
71 done();
72 });
73 });
74 });
75
76 describe("resolving invites to profile info", function() {
77 const syncData = {
78 next_batch: "s_5_3",
79 presence: {
80 events: [],
81 },
82 rooms: {
83 join: {
84
85 },
86 },
87 };
88
89 beforeEach(function() {
90 syncData.presence.events = [];
91 syncData.rooms.join[roomOne] = {
92 timeline: {
93 events: [
94 utils.mkMessage({
95 room: roomOne, user: otherUserId, msg: "hello",
96 }),
97 ],
98 },
99 state: {
100 events: [
101 utils.mkMembership({
102 room: roomOne, mship: "join", user: otherUserId,
103 }),
104 utils.mkMembership({
105 room: roomOne, mship: "join", user: selfUserId,
106 }),
107 utils.mkEvent({
108 type: "m.room.create", room: roomOne, user: selfUserId,
109 content: {
110 creator: selfUserId,
111 },
112 }),
113 ],
114 },
115 };
116 });
117
118 it("should resolve incoming invites from /sync", function() {
119 syncData.rooms.join[roomOne].state.events.push(
120 utils.mkMembership({
121 room: roomOne, mship: "invite", user: userC,
122 }),
123 );
124
125 httpBackend.when("GET", "/sync").respond(200, syncData);
126 httpBackend.when("GET", "/profile/" + encodeURIComponent(userC)).respond(
127 200, {
128 avatar_url: "mxc://flibble/wibble",
129 displayname: "The Boss",
130 },
131 );
132
133 client.startClient({
134 resolveInvitesToProfiles: true,
135 });
136
137
138 return Promise.all([
139 httpBackend.flushAllExpected(),
140 awaitSyncEvent(),
141 ]).then(function() {
142 const member = client.getRoom(roomOne).getMember(userC);
143 expect(member.name).toEqual("The Boss");
144 expect(
145 member.getAvatarUrl("home.server.url", null, null, null, false),
146 ).toBeTruthy();
147 });
148 });
149
150 it("should use cached values from m.presence wherever possible", function() {
151 syncData.presence.events = [
152 utils.mkPresence({
153 user: userC, presence: "online", name: "The Ghost",
154 }),
155 ];
156 syncData.rooms.join[roomOne].state.events.push(
157 utils.mkMembership({
158 room: roomOne, mship: "invite", user: userC,
159 }),
160 );
161
162 httpBackend.when("GET", "/sync").respond(200, syncData);
163
164 client.startClient({
165 resolveInvitesToProfiles: true,
166 });
167
168 return Promise.all([
169 httpBackend.flushAllExpected(),
170 awaitSyncEvent(),
171 ]).then(function() {
172 const member = client.getRoom(roomOne).getMember(userC);
173 expect(member.name).toEqual("The Ghost");
174 });
175 });
176
177 it("should result in events on the room member firing", function() {
178 syncData.presence.events = [
179 utils.mkPresence({
180 user: userC, presence: "online", name: "The Ghost",
181 }),
182 ];
183 syncData.rooms.join[roomOne].state.events.push(
184 utils.mkMembership({
185 room: roomOne, mship: "invite", user: userC,
186 }),
187 );
188
189 httpBackend.when("GET", "/sync").respond(200, syncData);
190
191 let latestFiredName = null;
192 client.on("RoomMember.name", function(event, m) {
193 if (m.userId === userC && m.roomId === roomOne) {
194 latestFiredName = m.name;
195 }
196 });
197
198 client.startClient({
199 resolveInvitesToProfiles: true,
200 });
201
202 return Promise.all([
203 httpBackend.flushAllExpected(),
204 awaitSyncEvent(),
205 ]).then(function() {
206 expect(latestFiredName).toEqual("The Ghost");
207 });
208 });
209
210 it("should no-op if resolveInvitesToProfiles is not set", function() {
211 syncData.rooms.join[roomOne].state.events.push(
212 utils.mkMembership({
213 room: roomOne, mship: "invite", user: userC,
214 }),
215 );
216
217 httpBackend.when("GET", "/sync").respond(200, syncData);
218
219 client.startClient();
220
221 return Promise.all([
222 httpBackend.flushAllExpected(),
223 awaitSyncEvent(),
224 ]).then(function() {
225 const member = client.getRoom(roomOne).getMember(userC);
226 expect(member.name).toEqual(userC);
227 expect(
228 member.getAvatarUrl("home.server.url", null, null, null, false),
229 ).toBe(null);
230 });
231 });
232 });
233
234 describe("users", function() {
235 const syncData = {
236 next_batch: "nb",
237 presence: {
238 events: [
239 utils.mkPresence({
240 user: userA, presence: "online",
241 }),
242 utils.mkPresence({
243 user: userB, presence: "unavailable",
244 }),
245 ],
246 },
247 };
248
249 it("should create users for presence events from /sync",
250 function() {
251 httpBackend.when("GET", "/sync").respond(200, syncData);
252
253 client.startClient();
254
255 return Promise.all([
256 httpBackend.flushAllExpected(),
257 awaitSyncEvent(),
258 ]).then(function() {
259 expect(client.getUser(userA).presence).toEqual("online");
260 expect(client.getUser(userB).presence).toEqual("unavailable");
261 });
262 });
263 });
264
265 describe("room state", function() {
266 const msgText = "some text here";
267 const otherDisplayName = "Bob Smith";
268
269 const syncData = {
270 rooms: {
271 join: {
272
273 },
274 },
275 };
276 syncData.rooms.join[roomOne] = {
277 timeline: {
278 events: [
279 utils.mkMessage({
280 room: roomOne, user: otherUserId, msg: "hello",
281 }),
282 ],
283 },
284 state: {
285 events: [
286 utils.mkEvent({
287 type: "m.room.name", room: roomOne, user: otherUserId,
288 content: {
289 name: "Old room name",
290 },
291 }),
292 utils.mkMembership({
293 room: roomOne, mship: "join", user: otherUserId,
294 }),
295 utils.mkMembership({
296 room: roomOne, mship: "join", user: selfUserId,
297 }),
298 utils.mkEvent({
299 type: "m.room.create", room: roomOne, user: selfUserId,
300 content: {
301 creator: selfUserId,
302 },
303 }),
304 ],
305 },
306 };
307 syncData.rooms.join[roomTwo] = {
308 timeline: {
309 events: [
310 utils.mkMessage({
311 room: roomTwo, user: otherUserId, msg: "hiii",
312 }),
313 ],
314 },
315 state: {
316 events: [
317 utils.mkMembership({
318 room: roomTwo, mship: "join", user: otherUserId,
319 name: otherDisplayName,
320 }),
321 utils.mkMembership({
322 room: roomTwo, mship: "join", user: selfUserId,
323 }),
324 utils.mkEvent({
325 type: "m.room.create", room: roomTwo, user: selfUserId,
326 content: {
327 creator: selfUserId,
328 },
329 }),
330 ],
331 },
332 };
333
334 const nextSyncData = {
335 rooms: {
336 join: {
337
338 },
339 },
340 };
341
342 nextSyncData.rooms.join[roomOne] = {
343 state: {
344 events: [
345 utils.mkEvent({
346 type: "m.room.name", room: roomOne, user: selfUserId,
347 content: { name: "A new room name" },
348 }),
349 ],
350 },
351 };
352
353 nextSyncData.rooms.join[roomTwo] = {
354 timeline: {
355 events: [
356 utils.mkMessage({
357 room: roomTwo, user: otherUserId, msg: msgText,
358 }),
359 ],
360 },
361 ephemeral: {
362 events: [
363 utils.mkEvent({
364 type: "m.typing", room: roomTwo,
365 content: { user_ids: [otherUserId] },
366 }),
367 ],
368 },
369 };
370
371 it("should continually recalculate the right room name.", function() {
372 httpBackend.when("GET", "/sync").respond(200, syncData);
373 httpBackend.when("GET", "/sync").respond(200, nextSyncData);
374
375 client.startClient();
376
377 return Promise.all([
378 httpBackend.flushAllExpected(),
379 awaitSyncEvent(2),
380 ]).then(function() {
381 const room = client.getRoom(roomOne);
382 // should have clobbered the name to the one from /events
383 expect(room.name).toEqual(
384 nextSyncData.rooms.join[roomOne].state.events[0].content.name,
385 );
386 });
387 });
388
389 it("should store the right events in the timeline.", function() {
390 httpBackend.when("GET", "/sync").respond(200, syncData);
391 httpBackend.when("GET", "/sync").respond(200, nextSyncData);
392
393 client.startClient();
394
395 return Promise.all([
396 httpBackend.flushAllExpected(),
397 awaitSyncEvent(2),
398 ]).then(function() {
399 const room = client.getRoom(roomTwo);
400 // should have added the message from /events
401 expect(room.timeline.length).toEqual(2);
402 expect(room.timeline[1].getContent().body).toEqual(msgText);
403 });
404 });
405
406 it("should set the right room name.", function() {
407 httpBackend.when("GET", "/sync").respond(200, syncData);
408 httpBackend.when("GET", "/sync").respond(200, nextSyncData);
409
410 client.startClient();
411 return Promise.all([
412 httpBackend.flushAllExpected(),
413 awaitSyncEvent(2),
414 ]).then(function() {
415 const room = client.getRoom(roomTwo);
416 // should use the display name of the other person.
417 expect(room.name).toEqual(otherDisplayName);
418 });
419 });
420
421 it("should set the right user's typing flag.", function() {
422 httpBackend.when("GET", "/sync").respond(200, syncData);
423 httpBackend.when("GET", "/sync").respond(200, nextSyncData);
424
425 client.startClient();
426
427 return Promise.all([
428 httpBackend.flushAllExpected(),
429 awaitSyncEvent(2),
430 ]).then(function() {
431 const room = client.getRoom(roomTwo);
432 let member = room.getMember(otherUserId);
433 expect(member).toBeTruthy();
434 expect(member.typing).toEqual(true);
435 member = room.getMember(selfUserId);
436 expect(member).toBeTruthy();
437 expect(member.typing).toEqual(false);
438 });
439 });
440
441 // XXX: This test asserts that the js-sdk obeys the spec and treats state
442 // events that arrive in the incremental sync as if they preceeded the
443 // timeline events, however this breaks peeking, so it's disabled
444 // (see sync.js)
445 xit("should correctly interpret state in incremental sync.", function() {
446 httpBackend.when("GET", "/sync").respond(200, syncData);
447 httpBackend.when("GET", "/sync").respond(200, nextSyncData);
448
449 client.startClient();
450 return Promise.all([
451 httpBackend.flushAllExpected(),
452 awaitSyncEvent(2),
453 ]).then(function() {
454 const room = client.getRoom(roomOne);
455 const stateAtStart = room.getLiveTimeline().getState(
456 EventTimeline.BACKWARDS,
457 );
458 const startRoomNameEvent = stateAtStart.getStateEvents('m.room.name', '');
459 expect(startRoomNameEvent.getContent().name).toEqual('Old room name');
460
461 const stateAtEnd = room.getLiveTimeline().getState(
462 EventTimeline.FORWARDS,
463 );
464 const endRoomNameEvent = stateAtEnd.getStateEvents('m.room.name', '');
465 expect(endRoomNameEvent.getContent().name).toEqual('A new room name');
466 });
467 });
468
469 xit("should update power levels for users in a room", function() {
470
471 });
472
473 xit("should update the room topic", function() {
474
475 });
476 });
477
478 describe("timeline", function() {
479 beforeEach(function() {
480 const syncData = {
481 next_batch: "batch_token",
482 rooms: {
483 join: {},
484 },
485 };
486 syncData.rooms.join[roomOne] = {
487 timeline: {
488 events: [
489 utils.mkMessage({
490 room: roomOne, user: otherUserId, msg: "hello",
491 }),
492 ],
493 prev_batch: "pagTok",
494 },
495 };
496
497 httpBackend.when("GET", "/sync").respond(200, syncData);
498
499 client.startClient();
500 return Promise.all([
501 httpBackend.flushAllExpected(),
502 awaitSyncEvent(),
503 ]);
504 });
505
506 it("should set the back-pagination token on new rooms", function() {
507 const syncData = {
508 next_batch: "batch_token",
509 rooms: {
510 join: {},
511 },
512 };
513 syncData.rooms.join[roomTwo] = {
514 timeline: {
515 events: [
516 utils.mkMessage({
517 room: roomTwo, user: otherUserId, msg: "roomtwo",
518 }),
519 ],
520 prev_batch: "roomtwotok",
521 },
522 };
523
524 httpBackend.when("GET", "/sync").respond(200, syncData);
525
526 return Promise.all([
527 httpBackend.flushAllExpected(),
528 awaitSyncEvent(),
529 ]).then(function() {
530 const room = client.getRoom(roomTwo);
531 expect(room).toExist();
532 const tok = room.getLiveTimeline()
533 .getPaginationToken(EventTimeline.BACKWARDS);
534 expect(tok).toEqual("roomtwotok");
535 });
536 });
537
538 it("should set the back-pagination token on gappy syncs", function() {
539 const syncData = {
540 next_batch: "batch_token",
541 rooms: {
542 join: {},
543 },
544 };
545 syncData.rooms.join[roomOne] = {
546 timeline: {
547 events: [
548 utils.mkMessage({
549 room: roomOne, user: otherUserId, msg: "world",
550 }),
551 ],
552 limited: true,
553 prev_batch: "newerTok",
554 },
555 };
556 httpBackend.when("GET", "/sync").respond(200, syncData);
557
558 let resetCallCount = 0;
559 // the token should be set *before* timelineReset is emitted
560 client.on("Room.timelineReset", function(room) {
561 resetCallCount++;
562
563 const tl = room.getLiveTimeline();
564 expect(tl.getEvents().length).toEqual(0);
565 const tok = tl.getPaginationToken(EventTimeline.BACKWARDS);
566 expect(tok).toEqual("newerTok");
567 });
568
569 return Promise.all([
570 httpBackend.flushAllExpected(),
571 awaitSyncEvent(),
572 ]).then(function() {
573 const room = client.getRoom(roomOne);
574 const tl = room.getLiveTimeline();
575 expect(tl.getEvents().length).toEqual(1);
576 expect(resetCallCount).toEqual(1);
577 });
578 });
579 });
580
581 describe("receipts", function() {
582 const syncData = {
583 rooms: {
584 join: {
585
586 },
587 },
588 };
589 syncData.rooms.join[roomOne] = {
590 timeline: {
591 events: [
592 utils.mkMessage({
593 room: roomOne, user: otherUserId, msg: "hello",
594 }),
595 utils.mkMessage({
596 room: roomOne, user: otherUserId, msg: "world",
597 }),
598 ],
599 },
600 state: {
601 events: [
602 utils.mkEvent({
603 type: "m.room.name", room: roomOne, user: otherUserId,
604 content: {
605 name: "Old room name",
606 },
607 }),
608 utils.mkMembership({
609 room: roomOne, mship: "join", user: otherUserId,
610 }),
611 utils.mkMembership({
612 room: roomOne, mship: "join", user: selfUserId,
613 }),
614 utils.mkEvent({
615 type: "m.room.create", room: roomOne, user: selfUserId,
616 content: {
617 creator: selfUserId,
618 },
619 }),
620 ],
621 },
622 };
623
624 beforeEach(function() {
625 syncData.rooms.join[roomOne].ephemeral = {
626 events: [],
627 };
628 });
629
630 it("should sync receipts from /sync.", function() {
631 const ackEvent = syncData.rooms.join[roomOne].timeline.events[0];
632 const receipt = {};
633 receipt[ackEvent.event_id] = {
634 "m.read": {},
635 };
636 receipt[ackEvent.event_id]["m.read"][userC] = {
637 ts: 176592842636,
638 };
639 syncData.rooms.join[roomOne].ephemeral.events = [{
640 content: receipt,
641 room_id: roomOne,
642 type: "m.receipt",
643 }];
644 httpBackend.when("GET", "/sync").respond(200, syncData);
645
646 client.startClient();
647
648 return Promise.all([
649 httpBackend.flushAllExpected(),
650 awaitSyncEvent(),
651 ]).then(function() {
652 const room = client.getRoom(roomOne);
653 expect(room.getReceiptsForEvent(new MatrixEvent(ackEvent))).toEqual([{
654 type: "m.read",
655 userId: userC,
656 data: {
657 ts: 176592842636,
658 },
659 }]);
660 });
661 });
662 });
663
664 describe("of a room", function() {
665 xit("should sync when a join event (which changes state) for the user" +
666 " arrives down the event stream (e.g. join from another device)", function() {
667
668 });
669
670 xit("should sync when the user explicitly calls joinRoom", function() {
671
672 });
673 });
674
675 describe("syncLeftRooms", function() {
676 beforeEach(function(done) {
677 client.startClient();
678
679 httpBackend.flushAllExpected().then(function() {
680 // the /sync call from syncLeftRooms ends up in the request
681 // queue behind the call from the running client; add a response
682 // to flush the client's one out.
683 httpBackend.when("GET", "/sync").respond(200, {});
684
685 done();
686 });
687 });
688
689 it("should create and use an appropriate filter", function() {
690 httpBackend.when("POST", "/filter").check(function(req) {
691 expect(req.data).toEqual({
692 room: { timeline: {limit: 1},
693 include_leave: true }});
694 }).respond(200, { filter_id: "another_id" });
695
696 const defer = Promise.defer();
697
698 httpBackend.when("GET", "/sync").check(function(req) {
699 expect(req.queryParams.filter).toEqual("another_id");
700 defer.resolve();
701 }).respond(200, {});
702
703 client.syncLeftRooms();
704
705 // first flush the filter request; this will make syncLeftRooms
706 // make its /sync call
707 return Promise.all([
708 httpBackend.flush("/filter").then(function() {
709 // flush the syncs
710 return httpBackend.flushAllExpected();
711 }),
712 defer.promise,
713 ]);
714 });
715
716 it("should set the back-pagination token on left rooms", function() {
717 const syncData = {
718 next_batch: "batch_token",
719 rooms: {
720 leave: {},
721 },
722 };
723
724 syncData.rooms.leave[roomTwo] = {
725 timeline: {
726 events: [
727 utils.mkMessage({
728 room: roomTwo, user: otherUserId, msg: "hello",
729 }),
730 ],
731 prev_batch: "pagTok",
732 },
733 };
734
735 httpBackend.when("POST", "/filter").respond(200, {
736 filter_id: "another_id",
737 });
738
739 httpBackend.when("GET", "/sync").respond(200, syncData);
740
741 return Promise.all([
742 client.syncLeftRooms().then(function() {
743 const room = client.getRoom(roomTwo);
744 const tok = room.getLiveTimeline().getPaginationToken(
745 EventTimeline.BACKWARDS);
746
747 expect(tok).toEqual("pagTok");
748 }),
749
750 // first flush the filter request; this will make syncLeftRooms
751 // make its /sync call
752 httpBackend.flush("/filter").then(function() {
753 return httpBackend.flushAllExpected();
754 }),
755 ]);
756 });
757 });
758
759 /**
760 * waits for the MatrixClient to emit one or more 'sync' events.
761 *
762 * @param {Number?} numSyncs number of syncs to wait for
763 * @returns {Promise} promise which resolves after the sync events have happened
764 */
765 function awaitSyncEvent(numSyncs) {
766 return utils.syncPromise(client, numSyncs);
767 }
768});