UNPKG

57.8 kBJavaScriptView Raw
1"use strict";
2import 'source-map-support/register';
3const sdk = require("../..");
4const Room = sdk.Room;
5const RoomState = sdk.RoomState;
6const MatrixEvent = sdk.MatrixEvent;
7const EventStatus = sdk.EventStatus;
8const EventTimeline = sdk.EventTimeline;
9const utils = require("../test-utils");
10
11import expect from 'expect';
12
13describe("Room", function() {
14 const roomId = "!foo:bar";
15 const userA = "@alice:bar";
16 const userB = "@bertha:bar";
17 const userC = "@clarissa:bar";
18 const userD = "@dorothy:bar";
19 let room;
20
21 beforeEach(function() {
22 utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
23 room = new Room(roomId);
24 // mock RoomStates
25 room.oldState = room.getLiveTimeline()._startState =
26 utils.mock(sdk.RoomState, "oldState");
27 room.currentState = room.getLiveTimeline()._endState =
28 utils.mock(sdk.RoomState, "currentState");
29 });
30
31 describe("getAvatarUrl", function() {
32 const hsUrl = "https://my.home.server";
33
34 it("should return the URL from m.room.avatar preferentially", function() {
35 room.currentState.getStateEvents.andCall(function(type, key) {
36 if (type === "m.room.avatar" && key === "") {
37 return utils.mkEvent({
38 event: true,
39 type: "m.room.avatar",
40 skey: "",
41 room: roomId,
42 user: userA,
43 content: {
44 url: "mxc://flibble/wibble",
45 },
46 });
47 }
48 });
49 const url = room.getAvatarUrl(hsUrl);
50 // we don't care about how the mxc->http conversion is done, other
51 // than it contains the mxc body.
52 expect(url.indexOf("flibble/wibble")).toNotEqual(-1);
53 });
54
55 it("should return an identicon HTTP URL if allowDefault was set and there " +
56 "was no m.room.avatar event", function() {
57 const url = room.getAvatarUrl(hsUrl, 64, 64, "crop", true);
58 expect(url.indexOf("http")).toEqual(0); // don't care about form
59 });
60
61 it("should return nothing if there is no m.room.avatar and allowDefault=false",
62 function() {
63 const url = room.getAvatarUrl(hsUrl, 64, 64, "crop", false);
64 expect(url).toEqual(null);
65 });
66 });
67
68 describe("getMember", function() {
69 beforeEach(function() {
70 room.currentState.getMember.andCall(function(userId) {
71 return {
72 "@alice:bar": {
73 userId: userA,
74 roomId: roomId,
75 },
76 }[userId];
77 });
78 });
79
80 it("should return null if the member isn't in current state", function() {
81 expect(room.getMember("@bar:foo")).toEqual(null);
82 });
83
84 it("should return the member from current state", function() {
85 expect(room.getMember(userA)).toNotEqual(null);
86 });
87 });
88
89 describe("addLiveEvents", function() {
90 const events = [
91 utils.mkMessage({
92 room: roomId, user: userA, msg: "changing room name", event: true,
93 }),
94 utils.mkEvent({
95 type: "m.room.name", room: roomId, user: userA, event: true,
96 content: { name: "New Room Name" },
97 }),
98 ];
99
100 it("should call RoomState.setTypingEvent on m.typing events", function() {
101 room.currentState = utils.mock(RoomState);
102 const typing = utils.mkEvent({
103 room: roomId, type: "m.typing", event: true, content: {
104 user_ids: [userA],
105 },
106 });
107 room.addEphemeralEvents([typing]);
108 expect(room.currentState.setTypingEvent).toHaveBeenCalledWith(typing);
109 });
110
111 it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function() {
112 expect(function() {
113 room.addLiveEvents(events, "foo");
114 }).toThrow();
115 });
116
117 it("should replace a timeline event if dupe strategy is 'replace'", function() {
118 // make a duplicate
119 const dupe = utils.mkMessage({
120 room: roomId, user: userA, msg: "dupe", event: true,
121 });
122 dupe.event.event_id = events[0].getId();
123 room.addLiveEvents(events);
124 expect(room.timeline[0]).toEqual(events[0]);
125 room.addLiveEvents([dupe], "replace");
126 expect(room.timeline[0]).toEqual(dupe);
127 });
128
129 it("should ignore a given dupe event if dupe strategy is 'ignore'", function() {
130 // make a duplicate
131 const dupe = utils.mkMessage({
132 room: roomId, user: userA, msg: "dupe", event: true,
133 });
134 dupe.event.event_id = events[0].getId();
135 room.addLiveEvents(events);
136 expect(room.timeline[0]).toEqual(events[0]);
137 room.addLiveEvents([dupe], "ignore");
138 expect(room.timeline[0]).toEqual(events[0]);
139 });
140
141 it("should emit 'Room.timeline' events",
142 function() {
143 let callCount = 0;
144 room.on("Room.timeline", function(event, emitRoom, toStart) {
145 callCount += 1;
146 expect(room.timeline.length).toEqual(callCount);
147 expect(event).toEqual(events[callCount - 1]);
148 expect(emitRoom).toEqual(room);
149 expect(toStart).toBeFalsy();
150 });
151 room.addLiveEvents(events);
152 expect(callCount).toEqual(2);
153 });
154
155 it("should call setStateEvents on the right RoomState with the right " +
156 "forwardLooking value for new events", function() {
157 const events = [
158 utils.mkMembership({
159 room: roomId, mship: "invite", user: userB, skey: userA, event: true,
160 }),
161 utils.mkEvent({
162 type: "m.room.name", room: roomId, user: userB, event: true,
163 content: {
164 name: "New room",
165 },
166 }),
167 ];
168 room.addLiveEvents(events);
169 expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
170 [events[0]],
171 );
172 expect(room.currentState.setStateEvents).toHaveBeenCalledWith(
173 [events[1]],
174 );
175 expect(events[0].forwardLooking).toBe(true);
176 expect(events[1].forwardLooking).toBe(true);
177 expect(room.oldState.setStateEvents).toNotHaveBeenCalled();
178 });
179
180 it("should synthesize read receipts for the senders of events", function() {
181 const sentinel = {
182 userId: userA,
183 membership: "join",
184 name: "Alice",
185 };
186 room.currentState.getSentinelMember.andCall(function(uid) {
187 if (uid === userA) {
188 return sentinel;
189 }
190 return null;
191 });
192 room.addLiveEvents(events);
193 expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId());
194 });
195
196 it("should emit Room.localEchoUpdated when a local echo is updated", function() {
197 const localEvent = utils.mkMessage({
198 room: roomId, user: userA, event: true,
199 });
200 localEvent.status = EventStatus.SENDING;
201 const localEventId = localEvent.getId();
202
203 const remoteEvent = utils.mkMessage({
204 room: roomId, user: userA, event: true,
205 });
206 remoteEvent.event.unsigned = {transaction_id: "TXN_ID"};
207 const remoteEventId = remoteEvent.getId();
208
209 let callCount = 0;
210 room.on("Room.localEchoUpdated",
211 function(event, emitRoom, oldEventId, oldStatus) {
212 switch (callCount) {
213 case 0:
214 expect(event.getId()).toEqual(localEventId);
215 expect(event.status).toEqual(EventStatus.SENDING);
216 expect(emitRoom).toEqual(room);
217 expect(oldEventId).toBe(null);
218 expect(oldStatus).toBe(null);
219 break;
220 case 1:
221 expect(event.getId()).toEqual(remoteEventId);
222 expect(event.status).toBe(null);
223 expect(emitRoom).toEqual(room);
224 expect(oldEventId).toEqual(localEventId);
225 expect(oldStatus).toBe(EventStatus.SENDING);
226 break;
227 }
228 callCount += 1;
229 },
230 );
231
232 // first add the local echo
233 room.addPendingEvent(localEvent, "TXN_ID");
234 expect(room.timeline.length).toEqual(1);
235
236 // then the remoteEvent
237 room.addLiveEvents([remoteEvent]);
238 expect(room.timeline.length).toEqual(1);
239
240 expect(callCount).toEqual(2);
241 });
242 });
243
244 describe("addEventsToTimeline", function() {
245 const events = [
246 utils.mkMessage({
247 room: roomId, user: userA, msg: "changing room name", event: true,
248 }),
249 utils.mkEvent({
250 type: "m.room.name", room: roomId, user: userA, event: true,
251 content: { name: "New Room Name" },
252 }),
253 ];
254
255 it("should not be able to add events to the end", function() {
256 expect(function() {
257 room.addEventsToTimeline(events, false, room.getLiveTimeline());
258 }).toThrow();
259 });
260
261 it("should be able to add events to the start", function() {
262 room.addEventsToTimeline(events, true, room.getLiveTimeline());
263 expect(room.timeline.length).toEqual(2);
264 expect(room.timeline[0]).toEqual(events[1]);
265 expect(room.timeline[1]).toEqual(events[0]);
266 });
267
268 it("should emit 'Room.timeline' events when added to the start",
269 function() {
270 let callCount = 0;
271 room.on("Room.timeline", function(event, emitRoom, toStart) {
272 callCount += 1;
273 expect(room.timeline.length).toEqual(callCount);
274 expect(event).toEqual(events[callCount - 1]);
275 expect(emitRoom).toEqual(room);
276 expect(toStart).toBe(true);
277 });
278 room.addEventsToTimeline(events, true, room.getLiveTimeline());
279 expect(callCount).toEqual(2);
280 });
281 });
282
283 describe("event metadata handling", function() {
284 it("should set event.sender for new and old events", function() {
285 const sentinel = {
286 userId: userA,
287 membership: "join",
288 name: "Alice",
289 };
290 const oldSentinel = {
291 userId: userA,
292 membership: "join",
293 name: "Old Alice",
294 };
295 room.currentState.getSentinelMember.andCall(function(uid) {
296 if (uid === userA) {
297 return sentinel;
298 }
299 return null;
300 });
301 room.oldState.getSentinelMember.andCall(function(uid) {
302 if (uid === userA) {
303 return oldSentinel;
304 }
305 return null;
306 });
307
308 const newEv = utils.mkEvent({
309 type: "m.room.name", room: roomId, user: userA, event: true,
310 content: { name: "New Room Name" },
311 });
312 const oldEv = utils.mkEvent({
313 type: "m.room.name", room: roomId, user: userA, event: true,
314 content: { name: "Old Room Name" },
315 });
316 room.addLiveEvents([newEv]);
317 expect(newEv.sender).toEqual(sentinel);
318 room.addEventsToTimeline([oldEv], true, room.getLiveTimeline());
319 expect(oldEv.sender).toEqual(oldSentinel);
320 });
321
322 it("should set event.target for new and old m.room.member events",
323 function() {
324 const sentinel = {
325 userId: userA,
326 membership: "join",
327 name: "Alice",
328 };
329 const oldSentinel = {
330 userId: userA,
331 membership: "join",
332 name: "Old Alice",
333 };
334 room.currentState.getSentinelMember.andCall(function(uid) {
335 if (uid === userA) {
336 return sentinel;
337 }
338 return null;
339 });
340 room.oldState.getSentinelMember.andCall(function(uid) {
341 if (uid === userA) {
342 return oldSentinel;
343 }
344 return null;
345 });
346
347 const newEv = utils.mkMembership({
348 room: roomId, mship: "invite", user: userB, skey: userA, event: true,
349 });
350 const oldEv = utils.mkMembership({
351 room: roomId, mship: "ban", user: userB, skey: userA, event: true,
352 });
353 room.addLiveEvents([newEv]);
354 expect(newEv.target).toEqual(sentinel);
355 room.addEventsToTimeline([oldEv], true, room.getLiveTimeline());
356 expect(oldEv.target).toEqual(oldSentinel);
357 });
358
359 it("should call setStateEvents on the right RoomState with the right " +
360 "forwardLooking value for old events", function() {
361 const events = [
362 utils.mkMembership({
363 room: roomId, mship: "invite", user: userB, skey: userA, event: true,
364 }),
365 utils.mkEvent({
366 type: "m.room.name", room: roomId, user: userB, event: true,
367 content: {
368 name: "New room",
369 },
370 }),
371 ];
372
373 room.addEventsToTimeline(events, true, room.getLiveTimeline());
374 expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
375 [events[0]],
376 );
377 expect(room.oldState.setStateEvents).toHaveBeenCalledWith(
378 [events[1]],
379 );
380 expect(events[0].forwardLooking).toBe(false);
381 expect(events[1].forwardLooking).toBe(false);
382 expect(room.currentState.setStateEvents).toNotHaveBeenCalled();
383 });
384 });
385
386 const resetTimelineTests = function(timelineSupport) {
387 let events = null;
388
389 beforeEach(function() {
390 room = new Room(roomId, null, null, {timelineSupport: timelineSupport});
391 // set events each time to avoid resusing Event objects (which
392 // doesn't work because they get frozen)
393 events = [
394 utils.mkMessage({
395 room: roomId, user: userA, msg: "A message", event: true,
396 }),
397 utils.mkEvent({
398 type: "m.room.name", room: roomId, user: userA, event: true,
399 content: { name: "New Room Name" },
400 }),
401 utils.mkEvent({
402 type: "m.room.name", room: roomId, user: userA, event: true,
403 content: { name: "Another New Name" },
404 }),
405 ];
406 });
407
408 it("should copy state from previous timeline", function() {
409 room.addLiveEvents([events[0], events[1]]);
410 expect(room.getLiveTimeline().getEvents().length).toEqual(2);
411 room.resetLiveTimeline('sometoken', 'someothertoken');
412
413 room.addLiveEvents([events[2]]);
414 const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS);
415 const newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
416 expect(room.getLiveTimeline().getEvents().length).toEqual(1);
417 expect(oldState.getStateEvents("m.room.name", "")).toEqual(events[1]);
418 expect(newState.getStateEvents("m.room.name", "")).toEqual(events[2]);
419 });
420
421 it("should reset the legacy timeline fields", function() {
422 room.addLiveEvents([events[0], events[1]]);
423 expect(room.timeline.length).toEqual(2);
424 room.resetLiveTimeline('sometoken', 'someothertoken');
425
426 room.addLiveEvents([events[2]]);
427 const newLiveTimeline = room.getLiveTimeline();
428 expect(room.timeline).toEqual(newLiveTimeline.getEvents());
429 expect(room.oldState).toEqual(
430 newLiveTimeline.getState(EventTimeline.BACKWARDS));
431 expect(room.currentState).toEqual(
432 newLiveTimeline.getState(EventTimeline.FORWARDS));
433 });
434
435 it("should emit Room.timelineReset event and set the correct " +
436 "pagination token", function() {
437 let callCount = 0;
438 room.on("Room.timelineReset", function(emitRoom) {
439 callCount += 1;
440 expect(emitRoom).toEqual(room);
441
442 // make sure that the pagination token has been set before the
443 // event is emitted.
444 const tok = emitRoom.getLiveTimeline()
445 .getPaginationToken(EventTimeline.BACKWARDS);
446
447 expect(tok).toEqual("pagToken");
448 });
449 room.resetLiveTimeline("pagToken");
450 expect(callCount).toEqual(1);
451 });
452
453 it("should " + (timelineSupport ? "remember" : "forget") +
454 " old timelines", function() {
455 room.addLiveEvents([events[0]]);
456 expect(room.timeline.length).toEqual(1);
457 const firstLiveTimeline = room.getLiveTimeline();
458 room.resetLiveTimeline('sometoken', 'someothertoken');
459
460 const tl = room.getTimelineForEvent(events[0].getId());
461 expect(tl).toBe(timelineSupport ? firstLiveTimeline : null);
462 });
463 };
464
465 describe("resetLiveTimeline with timelinesupport enabled",
466 resetTimelineTests.bind(null, true));
467 describe("resetLiveTimeline with timelinesupport disabled",
468 resetTimelineTests.bind(null, false));
469
470 describe("compareEventOrdering", function() {
471 beforeEach(function() {
472 room = new Room(roomId, null, null, {timelineSupport: true});
473 });
474
475 const events = [
476 utils.mkMessage({
477 room: roomId, user: userA, msg: "1111", event: true,
478 }),
479 utils.mkMessage({
480 room: roomId, user: userA, msg: "2222", event: true,
481 }),
482 utils.mkMessage({
483 room: roomId, user: userA, msg: "3333", event: true,
484 }),
485 ];
486
487 it("should handle events in the same timeline", function() {
488 room.addLiveEvents(events);
489
490 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
491 events[1].getId()))
492 .toBeLessThan(0);
493 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId(),
494 events[1].getId()))
495 .toBeGreaterThan(0);
496 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
497 events[1].getId()))
498 .toEqual(0);
499 });
500
501 it("should handle events in adjacent timelines", function() {
502 const oldTimeline = room.addTimeline();
503 oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), 'f');
504 room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, 'b');
505
506 room.addEventsToTimeline([events[0]], false, oldTimeline);
507 room.addLiveEvents([events[1]]);
508
509 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
510 events[1].getId()))
511 .toBeLessThan(0);
512 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
513 events[0].getId()))
514 .toBeGreaterThan(0);
515 });
516
517 it("should return null for events in non-adjacent timelines", function() {
518 const oldTimeline = room.addTimeline();
519
520 room.addEventsToTimeline([events[0]], false, oldTimeline);
521 room.addLiveEvents([events[1]]);
522
523 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(),
524 events[1].getId()))
525 .toBe(null);
526 expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(),
527 events[0].getId()))
528 .toBe(null);
529 });
530
531 it("should return null for unknown events", function() {
532 room.addLiveEvents(events);
533
534 expect(room.getUnfilteredTimelineSet()
535 .compareEventOrdering(events[0].getId(), "xxx"))
536 .toBe(null);
537 expect(room.getUnfilteredTimelineSet()
538 .compareEventOrdering("xxx", events[0].getId()))
539 .toBe(null);
540 expect(room.getUnfilteredTimelineSet()
541 .compareEventOrdering(events[0].getId(), events[0].getId()))
542 .toBe(0);
543 });
544 });
545
546 describe("getJoinedMembers", function() {
547 it("should return members whose membership is 'join'", function() {
548 room.currentState.getMembers.andCall(function() {
549 return [
550 { userId: "@alice:bar", membership: "join" },
551 { userId: "@bob:bar", membership: "invite" },
552 { userId: "@cleo:bar", membership: "leave" },
553 ];
554 });
555 const res = room.getJoinedMembers();
556 expect(res.length).toEqual(1);
557 expect(res[0].userId).toEqual("@alice:bar");
558 });
559
560 it("should return an empty list if no membership is 'join'", function() {
561 room.currentState.getMembers.andCall(function() {
562 return [
563 { userId: "@bob:bar", membership: "invite" },
564 ];
565 });
566 const res = room.getJoinedMembers();
567 expect(res.length).toEqual(0);
568 });
569 });
570
571 describe("hasMembershipState", function() {
572 it("should return true for a matching userId and membership",
573 function() {
574 room.currentState.getMember.andCall(function(userId) {
575 return {
576 "@alice:bar": { userId: "@alice:bar", membership: "join" },
577 "@bob:bar": { userId: "@bob:bar", membership: "invite" },
578 }[userId];
579 });
580 expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true);
581 });
582
583 it("should return false if match membership but no match userId",
584 function() {
585 room.currentState.getMember.andCall(function(userId) {
586 return {
587 "@alice:bar": { userId: "@alice:bar", membership: "join" },
588 }[userId];
589 });
590 expect(room.hasMembershipState("@bob:bar", "join")).toBe(false);
591 });
592
593 it("should return false if match userId but no match membership",
594 function() {
595 room.currentState.getMember.andCall(function(userId) {
596 return {
597 "@alice:bar": { userId: "@alice:bar", membership: "join" },
598 }[userId];
599 });
600 expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false);
601 });
602
603 it("should return false if no match membership or userId",
604 function() {
605 room.currentState.getMember.andCall(function(userId) {
606 return {
607 "@alice:bar": { userId: "@alice:bar", membership: "join" },
608 }[userId];
609 });
610 expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false);
611 });
612
613 it("should return false if no members exist",
614 function() {
615 expect(room.hasMembershipState("@foo:bar", "join")).toBe(false);
616 });
617 });
618
619 describe("recalculate", function() {
620 const setJoinRule = function(rule) {
621 room.addLiveEvents([utils.mkEvent({
622 type: "m.room.join_rules", room: roomId, user: userA, content: {
623 join_rule: rule,
624 }, event: true,
625 })]);
626 };
627 const setAliases = function(aliases, stateKey) {
628 if (!stateKey) {
629 stateKey = "flibble";
630 }
631 room.addLiveEvents([utils.mkEvent({
632 type: "m.room.aliases", room: roomId, skey: stateKey, content: {
633 aliases: aliases,
634 }, event: true,
635 })]);
636 };
637 const setRoomName = function(name) {
638 room.addLiveEvents([utils.mkEvent({
639 type: "m.room.name", room: roomId, user: userA, content: {
640 name: name,
641 }, event: true,
642 })]);
643 };
644 const addMember = function(userId, state, opts) {
645 if (!state) {
646 state = "join";
647 }
648 opts = opts || {};
649 opts.room = roomId;
650 opts.mship = state;
651 opts.user = opts.user || userId;
652 opts.skey = userId;
653 opts.event = true;
654 const event = utils.mkMembership(opts);
655 room.addLiveEvents([event]);
656 return event;
657 };
658
659 beforeEach(function() {
660 // no mocking
661 room = new Room(roomId, null, userA);
662 });
663
664 describe("Room.recalculate => Stripped State Events", function() {
665 it("should set stripped state events as actual state events if the " +
666 "room is an invite room", function() {
667 const roomName = "flibble";
668
669 const event = addMember(userA, "invite");
670 event.event.invite_room_state = [
671 {
672 type: "m.room.name",
673 state_key: "",
674 content: {
675 name: roomName,
676 },
677 },
678 ];
679
680 room.recalculate();
681 expect(room.name).toEqual(roomName);
682 });
683
684 it("should not clobber state events if it isn't an invite room", function() {
685 const event = addMember(userA, "join");
686 const roomName = "flibble";
687 setRoomName(roomName);
688 const roomNameToIgnore = "ignoreme";
689 event.event.invite_room_state = [
690 {
691 type: "m.room.name",
692 state_key: "",
693 content: {
694 name: roomNameToIgnore,
695 },
696 },
697 ];
698
699 room.recalculate();
700 expect(room.name).toEqual(roomName);
701 });
702 });
703
704 describe("Room.recalculate => Room Name using room summary", function() {
705 it("should use room heroes if available", function() {
706 addMember(userA, "invite");
707 addMember(userB);
708 addMember(userC);
709 addMember(userD);
710 room.setSummary({
711 "m.heroes": [userB, userC, userD],
712 });
713
714 room.recalculate();
715 expect(room.name).toEqual(`${userB} and 2 others`);
716 });
717
718 it("missing hero member state reverts to mxid", function() {
719 room.setSummary({
720 "m.heroes": [userB],
721 "m.joined_member_count": 2,
722 });
723
724 room.recalculate();
725 expect(room.name).toEqual(userB);
726 });
727
728 it("uses hero name from state", function() {
729 const name = "Mr B";
730 addMember(userA, "invite");
731 addMember(userB, "join", {name});
732 room.setSummary({
733 "m.heroes": [userB],
734 });
735
736 room.recalculate();
737 expect(room.name).toEqual(name);
738 });
739
740 it("uses counts from summary", function() {
741 const name = "Mr B";
742 addMember(userB, "join", {name});
743 room.setSummary({
744 "m.heroes": [userB],
745 "m.joined_member_count": 50,
746 "m.invited_member_count": 50,
747 });
748 room.recalculate();
749 expect(room.name).toEqual(`${name} and 98 others`);
750 });
751
752 it("relies on heroes in case of absent counts", function() {
753 const nameB = "Mr Bean";
754 const nameC = "Mel C";
755 addMember(userB, "join", {name: nameB});
756 addMember(userC, "join", {name: nameC});
757 room.setSummary({
758 "m.heroes": [userB, userC],
759 });
760 room.recalculate();
761 expect(room.name).toEqual(`${nameB} and ${nameC}`);
762 });
763
764 it("uses only heroes", function() {
765 const nameB = "Mr Bean";
766 addMember(userB, "join", {name: nameB});
767 addMember(userC, "join");
768 room.setSummary({
769 "m.heroes": [userB],
770 });
771 room.recalculate();
772 expect(room.name).toEqual(nameB);
773 });
774
775 it("reverts to empty room in case of self chat", function() {
776 room.setSummary({
777 "m.heroes": [],
778 "m.invited_member_count": 1,
779 });
780 room.recalculate();
781 expect(room.name).toEqual("Empty room");
782 });
783 });
784
785 describe("Room.recalculate => Room Name", function() {
786 it("should return the names of members in a private (invite join_rules)" +
787 " room if a room name and alias don't exist and there are >3 members.",
788 function() {
789 setJoinRule("invite");
790 addMember(userA);
791 addMember(userB);
792 addMember(userC);
793 addMember(userD);
794 room.recalculate();
795 const name = room.name;
796 // we expect at least 1 member to be mentioned
797 const others = [userB, userC, userD];
798 let found = false;
799 for (let i = 0; i < others.length; i++) {
800 if (name.indexOf(others[i]) !== -1) {
801 found = true;
802 break;
803 }
804 }
805 expect(found).toEqual(true, name);
806 });
807
808 it("should return the names of members in a private (invite join_rules)" +
809 " room if a room name and alias don't exist and there are >2 members.",
810 function() {
811 setJoinRule("invite");
812 addMember(userA);
813 addMember(userB);
814 addMember(userC);
815 room.recalculate();
816 const name = room.name;
817 expect(name.indexOf(userB)).toNotEqual(-1, name);
818 expect(name.indexOf(userC)).toNotEqual(-1, name);
819 });
820
821 it("should return the names of members in a public (public join_rules)" +
822 " room if a room name and alias don't exist and there are >2 members.",
823 function() {
824 setJoinRule("public");
825 addMember(userA);
826 addMember(userB);
827 addMember(userC);
828 room.recalculate();
829 const name = room.name;
830 expect(name.indexOf(userB)).toNotEqual(-1, name);
831 expect(name.indexOf(userC)).toNotEqual(-1, name);
832 });
833
834 it("should show the other user's name for public (public join_rules)" +
835 " rooms if a room name and alias don't exist and it is a 1:1-chat.",
836 function() {
837 setJoinRule("public");
838 addMember(userA);
839 addMember(userB);
840 room.recalculate();
841 const name = room.name;
842 expect(name.indexOf(userB)).toNotEqual(-1, name);
843 });
844
845 it("should show the other user's name for private " +
846 "(invite join_rules) rooms if a room name and alias don't exist and it" +
847 " is a 1:1-chat.", function() {
848 setJoinRule("invite");
849 addMember(userA);
850 addMember(userB);
851 room.recalculate();
852 const name = room.name;
853 expect(name.indexOf(userB)).toNotEqual(-1, name);
854 });
855
856 it("should show the other user's name for private" +
857 " (invite join_rules) rooms if you are invited to it.", function() {
858 setJoinRule("invite");
859 addMember(userA, "invite", {user: userB});
860 addMember(userB);
861 room.recalculate();
862 const name = room.name;
863 expect(name.indexOf(userB)).toNotEqual(-1, name);
864 });
865
866 it("should show the room alias if one exists for private " +
867 "(invite join_rules) rooms if a room name doesn't exist.", function() {
868 const alias = "#room_alias:here";
869 setJoinRule("invite");
870 setAliases([alias, "#another:one"]);
871 room.recalculate();
872 const name = room.name;
873 expect(name).toEqual(alias);
874 });
875
876 it("should show the room alias if one exists for public " +
877 "(public join_rules) rooms if a room name doesn't exist.", function() {
878 const alias = "#room_alias:here";
879 setJoinRule("public");
880 setAliases([alias, "#another:one"]);
881 room.recalculate();
882 const name = room.name;
883 expect(name).toEqual(alias);
884 });
885
886 it("should show the room name if one exists for private " +
887 "(invite join_rules) rooms.", function() {
888 const roomName = "A mighty name indeed";
889 setJoinRule("invite");
890 setRoomName(roomName);
891 room.recalculate();
892 const name = room.name;
893 expect(name).toEqual(roomName);
894 });
895
896 it("should show the room name if one exists for public " +
897 "(public join_rules) rooms.", function() {
898 const roomName = "A mighty name indeed";
899 setJoinRule("public");
900 setRoomName(roomName);
901 room.recalculate();
902 expect(room.name).toEqual(roomName);
903 });
904
905 it("should return 'Empty room' for private (invite join_rules) rooms if" +
906 " a room name and alias don't exist and it is a self-chat.", function() {
907 setJoinRule("invite");
908 addMember(userA);
909 room.recalculate();
910 expect(room.name).toEqual("Empty room");
911 });
912
913 it("should return 'Empty room' for public (public join_rules) rooms if a" +
914 " room name and alias don't exist and it is a self-chat.", function() {
915 setJoinRule("public");
916 addMember(userA);
917 room.recalculate();
918 const name = room.name;
919 expect(name).toEqual("Empty room");
920 });
921
922 it("should return 'Empty room' if there is no name, " +
923 "alias or members in the room.",
924 function() {
925 room.recalculate();
926 const name = room.name;
927 expect(name).toEqual("Empty room");
928 });
929
930 it("should return '[inviter display name] if state event " +
931 "available",
932 function() {
933 setJoinRule("invite");
934 addMember(userB, 'join', {name: "Alice"});
935 addMember(userA, "invite", {user: userA});
936 room.recalculate();
937 const name = room.name;
938 expect(name).toEqual("Alice");
939 });
940
941 it("should return inviter mxid if display name not available",
942 function() {
943 setJoinRule("invite");
944 addMember(userB);
945 addMember(userA, "invite", {user: userA});
946 room.recalculate();
947 const name = room.name;
948 expect(name).toEqual(userB);
949 });
950 });
951 });
952
953 describe("receipts", function() {
954 const eventToAck = utils.mkMessage({
955 room: roomId, user: userA, msg: "PLEASE ACKNOWLEDGE MY EXISTENCE",
956 event: true,
957 });
958
959 function mkReceipt(roomId, records) {
960 const content = {};
961 records.forEach(function(r) {
962 if (!content[r.eventId]) {
963 content[r.eventId] = {};
964 }
965 if (!content[r.eventId][r.type]) {
966 content[r.eventId][r.type] = {};
967 }
968 content[r.eventId][r.type][r.userId] = {
969 ts: r.ts,
970 };
971 });
972 return new MatrixEvent({
973 content: content,
974 room_id: roomId,
975 type: "m.receipt",
976 });
977 }
978
979 function mkRecord(eventId, type, userId, ts) {
980 ts = ts || Date.now();
981 return {
982 eventId: eventId,
983 type: type,
984 userId: userId,
985 ts: ts,
986 };
987 }
988
989 describe("addReceipt", function() {
990 it("should store the receipt so it can be obtained via getReceiptsForEvent",
991 function() {
992 const ts = 13787898424;
993 room.addReceipt(mkReceipt(roomId, [
994 mkRecord(eventToAck.getId(), "m.read", userB, ts),
995 ]));
996 expect(room.getReceiptsForEvent(eventToAck)).toEqual([{
997 type: "m.read",
998 userId: userB,
999 data: {
1000 ts: ts,
1001 },
1002 }]);
1003 });
1004
1005 it("should emit an event when a receipt is added",
1006 function() {
1007 const listener = expect.createSpy();
1008 room.on("Room.receipt", listener);
1009
1010 const ts = 13787898424;
1011
1012 const receiptEvent = mkReceipt(roomId, [
1013 mkRecord(eventToAck.getId(), "m.read", userB, ts),
1014 ]);
1015
1016 room.addReceipt(receiptEvent);
1017 expect(listener).toHaveBeenCalledWith(receiptEvent, room);
1018 });
1019
1020 it("should clobber receipts based on type and user ID", function() {
1021 const nextEventToAck = utils.mkMessage({
1022 room: roomId, user: userA, msg: "I AM HERE YOU KNOW",
1023 event: true,
1024 });
1025 const ts = 13787898424;
1026 room.addReceipt(mkReceipt(roomId, [
1027 mkRecord(eventToAck.getId(), "m.read", userB, ts),
1028 ]));
1029 const ts2 = 13787899999;
1030 room.addReceipt(mkReceipt(roomId, [
1031 mkRecord(nextEventToAck.getId(), "m.read", userB, ts2),
1032 ]));
1033 expect(room.getReceiptsForEvent(eventToAck)).toEqual([]);
1034 expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{
1035 type: "m.read",
1036 userId: userB,
1037 data: {
1038 ts: ts2,
1039 },
1040 }]);
1041 });
1042
1043 it("should persist multiple receipts for a single event ID", function() {
1044 const ts = 13787898424;
1045 room.addReceipt(mkReceipt(roomId, [
1046 mkRecord(eventToAck.getId(), "m.read", userB, ts),
1047 mkRecord(eventToAck.getId(), "m.read", userC, ts),
1048 mkRecord(eventToAck.getId(), "m.read", userD, ts),
1049 ]));
1050 expect(room.getUsersReadUpTo(eventToAck)).toEqual(
1051 [userB, userC, userD],
1052 );
1053 });
1054
1055 it("should persist multiple receipts for a single receipt type", function() {
1056 const eventTwo = utils.mkMessage({
1057 room: roomId, user: userA, msg: "2222",
1058 event: true,
1059 });
1060 const eventThree = utils.mkMessage({
1061 room: roomId, user: userA, msg: "3333",
1062 event: true,
1063 });
1064 const ts = 13787898424;
1065 room.addReceipt(mkReceipt(roomId, [
1066 mkRecord(eventToAck.getId(), "m.read", userB, ts),
1067 mkRecord(eventTwo.getId(), "m.read", userC, ts),
1068 mkRecord(eventThree.getId(), "m.read", userD, ts),
1069 ]));
1070 expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
1071 expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]);
1072 expect(room.getUsersReadUpTo(eventThree)).toEqual([userD]);
1073 });
1074
1075 it("should persist multiple receipts for a single user ID", function() {
1076 room.addReceipt(mkReceipt(roomId, [
1077 mkRecord(eventToAck.getId(), "m.delivered", userB, 13787898424),
1078 mkRecord(eventToAck.getId(), "m.read", userB, 22222222),
1079 mkRecord(eventToAck.getId(), "m.seen", userB, 33333333),
1080 ]));
1081 expect(room.getReceiptsForEvent(eventToAck)).toEqual([
1082 {
1083 type: "m.delivered",
1084 userId: userB,
1085 data: {
1086 ts: 13787898424,
1087 },
1088 },
1089 {
1090 type: "m.read",
1091 userId: userB,
1092 data: {
1093 ts: 22222222,
1094 },
1095 },
1096 {
1097 type: "m.seen",
1098 userId: userB,
1099 data: {
1100 ts: 33333333,
1101 },
1102 },
1103 ]);
1104 });
1105
1106 it("should prioritise the most recent event", function() {
1107 const events = [
1108 utils.mkMessage({
1109 room: roomId, user: userA, msg: "1111",
1110 event: true,
1111 }),
1112 utils.mkMessage({
1113 room: roomId, user: userA, msg: "2222",
1114 event: true,
1115 }),
1116 utils.mkMessage({
1117 room: roomId, user: userA, msg: "3333",
1118 event: true,
1119 }),
1120 ];
1121
1122 room.addLiveEvents(events);
1123 const ts = 13787898424;
1124
1125 // check it initialises correctly
1126 room.addReceipt(mkReceipt(roomId, [
1127 mkRecord(events[0].getId(), "m.read", userB, ts),
1128 ]));
1129 expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId());
1130
1131 // 2>0, so it should move forward
1132 room.addReceipt(mkReceipt(roomId, [
1133 mkRecord(events[2].getId(), "m.read", userB, ts),
1134 ]));
1135 expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
1136
1137 // 1<2, so it should stay put
1138 room.addReceipt(mkReceipt(roomId, [
1139 mkRecord(events[1].getId(), "m.read", userB, ts),
1140 ]));
1141 expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId());
1142 });
1143 });
1144
1145 describe("getUsersReadUpTo", function() {
1146 it("should return user IDs read up to the given event", function() {
1147 const ts = 13787898424;
1148 room.addReceipt(mkReceipt(roomId, [
1149 mkRecord(eventToAck.getId(), "m.read", userB, ts),
1150 ]));
1151 expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]);
1152 });
1153 });
1154 });
1155
1156 describe("tags", function() {
1157 function mkTags(roomId, tags) {
1158 const content = { "tags": tags };
1159 return new MatrixEvent({
1160 content: content,
1161 room_id: roomId,
1162 type: "m.tag",
1163 });
1164 }
1165
1166 describe("addTag", function() {
1167 it("should set tags on rooms from event stream so " +
1168 "they can be obtained by the tags property",
1169 function() {
1170 const tags = { "m.foo": { "order": 0.5 } };
1171 room.addTags(mkTags(roomId, tags));
1172 expect(room.tags).toEqual(tags);
1173 });
1174
1175 it("should emit Room.tags event when new tags are " +
1176 "received on the event stream",
1177 function() {
1178 const listener = expect.createSpy();
1179 room.on("Room.tags", listener);
1180
1181 const tags = { "m.foo": { "order": 0.5 } };
1182 const event = mkTags(roomId, tags);
1183 room.addTags(event);
1184 expect(listener).toHaveBeenCalledWith(event, room);
1185 });
1186
1187 // XXX: shouldn't we try injecting actual m.tag events onto the eventstream
1188 // rather than injecting via room.addTags()?
1189 });
1190 });
1191
1192 describe("addPendingEvent", function() {
1193 it("should add pending events to the pendingEventList if " +
1194 "pendingEventOrdering == 'detached'", function() {
1195 const room = new Room(roomId, null, userA, {
1196 pendingEventOrdering: "detached",
1197 });
1198 const eventA = utils.mkMessage({
1199 room: roomId, user: userA, msg: "remote 1", event: true,
1200 });
1201 const eventB = utils.mkMessage({
1202 room: roomId, user: userA, msg: "local 1", event: true,
1203 });
1204 eventB.status = EventStatus.SENDING;
1205 const eventC = utils.mkMessage({
1206 room: roomId, user: userA, msg: "remote 2", event: true,
1207 });
1208 room.addLiveEvents([eventA]);
1209 room.addPendingEvent(eventB, "TXN1");
1210 room.addLiveEvents([eventC]);
1211 expect(room.timeline).toEqual(
1212 [eventA, eventC],
1213 );
1214 expect(room.getPendingEvents()).toEqual(
1215 [eventB],
1216 );
1217 });
1218
1219 it("should add pending events to the timeline if " +
1220 "pendingEventOrdering == 'chronological'", function() {
1221 room = new Room(roomId, null, userA, {
1222 pendingEventOrdering: "chronological",
1223 });
1224 const eventA = utils.mkMessage({
1225 room: roomId, user: userA, msg: "remote 1", event: true,
1226 });
1227 const eventB = utils.mkMessage({
1228 room: roomId, user: userA, msg: "local 1", event: true,
1229 });
1230 eventB.status = EventStatus.SENDING;
1231 const eventC = utils.mkMessage({
1232 room: roomId, user: userA, msg: "remote 2", event: true,
1233 });
1234 room.addLiveEvents([eventA]);
1235 room.addPendingEvent(eventB, "TXN1");
1236 room.addLiveEvents([eventC]);
1237 expect(room.timeline).toEqual(
1238 [eventA, eventB, eventC],
1239 );
1240 });
1241 });
1242
1243 describe("updatePendingEvent", function() {
1244 it("should remove cancelled events from the pending list", function() {
1245 const room = new Room(roomId, null, userA, {
1246 pendingEventOrdering: "detached",
1247 });
1248 const eventA = utils.mkMessage({
1249 room: roomId, user: userA, event: true,
1250 });
1251 eventA.status = EventStatus.SENDING;
1252 const eventId = eventA.getId();
1253
1254 room.addPendingEvent(eventA, "TXN1");
1255 expect(room.getPendingEvents()).toEqual(
1256 [eventA],
1257 );
1258
1259 // the event has to have been failed or queued before it can be
1260 // cancelled
1261 room.updatePendingEvent(eventA, EventStatus.NOT_SENT);
1262
1263 let callCount = 0;
1264 room.on("Room.localEchoUpdated",
1265 function(event, emitRoom, oldEventId, oldStatus) {
1266 expect(event).toEqual(eventA);
1267 expect(event.status).toEqual(EventStatus.CANCELLED);
1268 expect(emitRoom).toEqual(room);
1269 expect(oldEventId).toEqual(eventId);
1270 expect(oldStatus).toEqual(EventStatus.NOT_SENT);
1271 callCount++;
1272 });
1273
1274 room.updatePendingEvent(eventA, EventStatus.CANCELLED);
1275 expect(room.getPendingEvents()).toEqual([]);
1276 expect(callCount).toEqual(1);
1277 });
1278
1279
1280 it("should remove cancelled events from the timeline", function() {
1281 const room = new Room(roomId, null, userA);
1282 const eventA = utils.mkMessage({
1283 room: roomId, user: userA, event: true,
1284 });
1285 eventA.status = EventStatus.SENDING;
1286 const eventId = eventA.getId();
1287
1288 room.addPendingEvent(eventA, "TXN1");
1289 expect(room.getLiveTimeline().getEvents()).toEqual(
1290 [eventA],
1291 );
1292
1293 // the event has to have been failed or queued before it can be
1294 // cancelled
1295 room.updatePendingEvent(eventA, EventStatus.NOT_SENT);
1296
1297 let callCount = 0;
1298 room.on("Room.localEchoUpdated",
1299 function(event, emitRoom, oldEventId, oldStatus) {
1300 expect(event).toEqual(eventA);
1301 expect(event.status).toEqual(EventStatus.CANCELLED);
1302 expect(emitRoom).toEqual(room);
1303 expect(oldEventId).toEqual(eventId);
1304 expect(oldStatus).toEqual(EventStatus.NOT_SENT);
1305 callCount++;
1306 });
1307
1308 room.updatePendingEvent(eventA, EventStatus.CANCELLED);
1309 expect(room.getLiveTimeline().getEvents()).toEqual([]);
1310 expect(callCount).toEqual(1);
1311 });
1312 });
1313
1314 describe("loadMembersIfNeeded", function() {
1315 function createClientMock(serverResponse, storageResponse = null) {
1316 return {
1317 getEventMapper: function() {
1318 // events should already be MatrixEvents
1319 return function(event) {return event;};
1320 },
1321 isCryptoEnabled() {
1322 return true;
1323 },
1324 isRoomEncrypted: function() {
1325 return false;
1326 },
1327 _http: {
1328 serverResponse,
1329 authedRequest: function() {
1330 if (this.serverResponse instanceof Error) {
1331 return Promise.reject(this.serverResponse);
1332 } else {
1333 return Promise.resolve({chunk: this.serverResponse});
1334 }
1335 },
1336 },
1337 store: {
1338 storageResponse,
1339 storedMembers: null,
1340 getOutOfBandMembers: function() {
1341 if (this.storageResponse instanceof Error) {
1342 return Promise.reject(this.storageResponse);
1343 } else {
1344 return Promise.resolve(this.storageResponse);
1345 }
1346 },
1347 setOutOfBandMembers: function(roomId, memberEvents) {
1348 this.storedMembers = memberEvents;
1349 return Promise.resolve();
1350 },
1351 getSyncToken: () => "sync_token",
1352 },
1353 };
1354 }
1355
1356 const memberEvent = utils.mkMembership({
1357 user: "@user_a:bar", mship: "join",
1358 room: roomId, event: true, name: "User A",
1359 });
1360
1361 it("should load members from server on first call", async function() {
1362 const client = createClientMock([memberEvent]);
1363 const room = new Room(roomId, client, null, {lazyLoadMembers: true});
1364 await room.loadMembersIfNeeded();
1365 const memberA = room.getMember("@user_a:bar");
1366 expect(memberA.name).toEqual("User A");
1367 const storedMembers = client.store.storedMembers;
1368 expect(storedMembers.length).toEqual(1);
1369 expect(storedMembers[0].event_id).toEqual(memberEvent.getId());
1370 });
1371
1372 it("should take members from storage if available", async function() {
1373 const memberEvent2 = utils.mkMembership({
1374 user: "@user_a:bar", mship: "join",
1375 room: roomId, event: true, name: "Ms A",
1376 });
1377 const client = createClientMock([memberEvent2], [memberEvent]);
1378 const room = new Room(roomId, client, null, {lazyLoadMembers: true});
1379
1380 await room.loadMembersIfNeeded();
1381
1382 const memberA = room.getMember("@user_a:bar");
1383 expect(memberA.name).toEqual("User A");
1384 });
1385
1386 it("should allow retry on error", async function() {
1387 const client = createClientMock(new Error("server says no"));
1388 const room = new Room(roomId, client, null, {lazyLoadMembers: true});
1389 let hasThrown = false;
1390 try {
1391 await room.loadMembersIfNeeded();
1392 } catch(err) {
1393 hasThrown = true;
1394 }
1395 expect(hasThrown).toEqual(true);
1396
1397 client._http.serverResponse = [memberEvent];
1398 await room.loadMembersIfNeeded();
1399 const memberA = room.getMember("@user_a:bar");
1400 expect(memberA.name).toEqual("User A");
1401 });
1402 });
1403
1404 describe("getMyMembership", function() {
1405 it("should return synced membership if membership isn't available yet",
1406 function() {
1407 const room = new Room(roomId, null, userA);
1408 room.updateMyMembership("invite");
1409 expect(room.getMyMembership()).toEqual("invite");
1410 });
1411 it("should emit a Room.myMembership event on a change",
1412 function() {
1413 const room = new Room(roomId, null, userA);
1414 const events = [];
1415 room.on("Room.myMembership", (_room, membership, oldMembership) => {
1416 events.push({membership, oldMembership});
1417 });
1418 room.updateMyMembership("invite");
1419 expect(room.getMyMembership()).toEqual("invite");
1420 expect(events[0]).toEqual({membership: "invite", oldMembership: null});
1421 events.splice(0); //clear
1422 room.updateMyMembership("invite");
1423 expect(events.length).toEqual(0);
1424 room.updateMyMembership("join");
1425 expect(room.getMyMembership()).toEqual("join");
1426 expect(events[0]).toEqual({membership: "join", oldMembership: "invite"});
1427 });
1428 });
1429
1430 describe("guessDMUserId", function() {
1431 it("should return first hero id",
1432 function() {
1433 const room = new Room(roomId, null, userA);
1434 room.setSummary({'m.heroes': [userB]});
1435 expect(room.guessDMUserId()).toEqual(userB);
1436 });
1437 it("should return first member that isn't self",
1438 function() {
1439 const room = new Room(roomId, null, userA);
1440 room.addLiveEvents([utils.mkMembership({
1441 user: userB, mship: "join",
1442 room: roomId, event: true,
1443 })]);
1444 expect(room.guessDMUserId()).toEqual(userB);
1445 });
1446 it("should return self if only member present",
1447 function() {
1448 const room = new Room(roomId, null, userA);
1449 expect(room.guessDMUserId()).toEqual(userA);
1450 });
1451 });
1452
1453 describe("maySendMessage", function() {
1454 it("should return false if synced membership not join",
1455 function() {
1456 const room = new Room(roomId, null, userA);
1457 room.updateMyMembership("invite");
1458 expect(room.maySendMessage()).toEqual(false);
1459 room.updateMyMembership("leave");
1460 expect(room.maySendMessage()).toEqual(false);
1461 room.updateMyMembership("join");
1462 expect(room.maySendMessage()).toEqual(true);
1463 });
1464 });
1465});