diff --git a/spec/test-utils.js b/spec/test-utils.js index ed53edc35..2362aa7c9 100644 --- a/spec/test-utils.js +++ b/spec/test-utils.js @@ -11,6 +11,36 @@ module.exports.beforeEach = function(testCase) { console.log(new Array(1 + desc.length).join("=")); }; +/** + * Create a spy for an object and automatically spy its methods. + * @param {*} constr The class constructor (used with 'new') + * @param {string} name The name of the class + * @return {Object} An instantiated object with spied methods/properties. + */ +module.exports.mock = function(constr, name) { + // By Tim Buschtöns + // http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/ + var HelperConstr = new Function(); // jshint ignore:line + HelperConstr.prototype = constr.prototype; + var result = new HelperConstr(); + result.jasmineToString = function() { + return "mock" + (name ? " of " + name : ""); + }; + for (var key in constr.prototype) { // jshint ignore:line + try { + if (constr.prototype[key] instanceof Function) { + result[key] = jasmine.createSpy((name || "mock") + '.' + key); + } + } + catch (ex) { + // Direct access to some non-function fields of DOM prototypes may + // cause exceptions. + // Overwriting will not work either in that case. + } + } + return result; +}; + /** * Create a JSON object representing an Event. * @param {string} type The event.type diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index 4a539ed4a..19aa35bc3 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -15,10 +15,14 @@ describe("Room", function() { beforeEach(function() { utils.beforeEach(this); room = new Room(roomId); + // mock RoomStates + room.oldState = utils.mock(sdk.RoomState, "oldState"); + room.currentState = utils.mock(sdk.RoomState, "currentState"); }); describe("getMember", function() { beforeEach(function() { + // clobber members property with test data room.currentState.members = { "@alice:bar": { userId: userA, @@ -43,6 +47,7 @@ describe("Room", function() { name: "New Room Name" })) ]; + it("should be able to add events to the end", function() { room.addEventsToTimeline(events); expect(room.timeline.length).toEqual(2); @@ -56,43 +61,191 @@ describe("Room", function() { expect(room.timeline[0]).toEqual(events[1]); expect(room.timeline[1]).toEqual(events[0]); }); + + it("should emit 'Room.timeline' events when added to the end", + function() { + var callCount = 0; + room.on("Room.timeline", function(event, emitRoom, toStart) { + callCount += 1; + expect(room.timeline.length).toEqual(callCount); + expect(event).toEqual(events[callCount - 1]); + expect(emitRoom).toEqual(room); + expect(toStart).toBeFalsy(); + }); + room.addEventsToTimeline(events); + expect(callCount).toEqual(2); + }); + + it("should emit 'Room.timeline' events when added to the start", + function() { + var callCount = 0; + room.on("Room.timeline", function(event, emitRoom, toStart) { + callCount += 1; + expect(room.timeline.length).toEqual(callCount); + expect(event).toEqual(events[callCount - 1]); + expect(emitRoom).toEqual(room); + expect(toStart).toBe(true); + }); + room.addEventsToTimeline(events, true); + expect(callCount).toEqual(2); + }); + + it("should set event.sender for new and old events", function() { + var sentinel = { + userId: userA, + membership: "join", + name: "Alice" + }; + var oldSentinel = { + userId: userA, + membership: "join", + name: "Old Alice" + }; + room.currentState.getSentinelMember.andCallFake(function(uid) { + if (uid === userA) { + return sentinel; + } + return null; + }); + room.oldState.getSentinelMember.andCallFake(function(uid) { + if (uid === userA) { + return oldSentinel; + } + return null; + }); + + var newEv = new MatrixEvent(utils.mkEvent("m.room.name", roomId, userA, { + name: "New Room Name" + })); + var oldEv = new MatrixEvent(utils.mkEvent("m.room.name", roomId, userA, { + name: "Old Room Name" + })); + room.addEventsToTimeline([newEv]); + expect(newEv.sender).toEqual(sentinel); + room.addEventsToTimeline([oldEv], true); + expect(oldEv.sender).toEqual(oldSentinel); + }); + + it("should set event.target for new and old m.room.member events", + function() { + var sentinel = { + userId: userA, + membership: "join", + name: "Alice" + }; + var oldSentinel = { + userId: userA, + membership: "join", + name: "Old Alice" + }; + room.currentState.getSentinelMember.andCallFake(function(uid) { + if (uid === userA) { + return sentinel; + } + return null; + }); + room.oldState.getSentinelMember.andCallFake(function(uid) { + if (uid === userA) { + return oldSentinel; + } + return null; + }); + + var newEv = new MatrixEvent( + utils.mkMembership(roomId, "invite", userB, userA) + ); + var oldEv = new MatrixEvent( + utils.mkMembership(roomId, "ban", userB, userA) + ); + room.addEventsToTimeline([newEv]); + expect(newEv.target).toEqual(sentinel); + room.addEventsToTimeline([oldEv], true); + expect(oldEv.target).toEqual(oldSentinel); + }); }); - describe("calculate (Room Name)", function() { + describe("getJoinedMembers", function() { + + it("should return members whose membership is 'join'", function() { + room.currentState.getMembers.andCallFake(function() { + return [ + { userId: "@alice:bar", membership: "join" }, + { userId: "@bob:bar", membership: "invite" }, + { userId: "@cleo:bar", membership: "leave" } + ]; + }); + var res = room.getJoinedMembers(); + expect(res.length).toEqual(1); + expect(res[0].userId).toEqual("@alice:bar"); + }); + + it("should return an empty list if no membership is 'join'", function() { + room.currentState.getMembers.andCallFake(function() { + return [ + { userId: "@bob:bar", membership: "invite" } + ]; + }); + var res = room.getJoinedMembers(); + expect(res.length).toEqual(0); + }); + }); + + describe("hasMembershipState", function() { + + it("should return true for a matching userId and membership", + function() { + room.currentState.getMembers.andCallFake(function() { + return [ + { userId: "@alice:bar", membership: "join" }, + { userId: "@bob:bar", membership: "invite" } + ]; + }); + expect(room.hasMembershipState("@bob:bar", "invite")).toBe(true); + }); + + it("should return false if match membership but no match userId", + function() { + room.currentState.getMembers.andCallFake(function() { + return [ + { userId: "@alice:bar", membership: "join" } + ]; + }); + expect(room.hasMembershipState("@bob:bar", "join")).toBe(false); + }); + + it("should return false if match userId but no match membership", + function() { + room.currentState.getMembers.andCallFake(function() { + return [ + { userId: "@alice:bar", membership: "join" } + ]; + }); + expect(room.hasMembershipState("@alice:bar", "ban")).toBe(false); + }); + + it("should return false if no match membership or userId", + function() { + room.currentState.getMembers.andCallFake(function() { + return [ + { userId: "@alice:bar", membership: "join" } + ]; + }); + expect(room.hasMembershipState("@bob:bar", "invite")).toBe(false); + }); + + it("should return false if no members exist", + function() { + room.currentState.getMembers.andCallFake(function() { + return []; + }); + expect(room.hasMembershipState("@foo:bar", "join")).toBe(false); + }); + }); + + describe("recalculate (Room Name)", function() { var stateLookup = { // event.type + "$" event.state_key : MatrixEvent }; - var mockRoomState = { - getStateEvents: function(type, key) { - if (key === undefined) { - var prefix = type + "$"; - var list = []; - for (var stateBlob in stateLookup) { - if (!stateLookup.hasOwnProperty(stateBlob)) { continue; } - if (stateBlob.indexOf(prefix) === 0) { - list.push(stateLookup[stateBlob]); - } - } - return list; - } - else { - return stateLookup[type + "$" + key]; - } - }, - getMembers: function() { - var memberEvents = this.getStateEvents("m.room.member"); - var members = []; - for (var i = 0; i < memberEvents.length; i++) { - members.push({ - // not interested in user ID vs display name semantics. - // That should be tested in RoomMember UTs. - name: memberEvents[i].getSender(), - userId: memberEvents[i].getSender() - }); - } - return members; - } - }; var setJoinRule = function(rule) { stateLookup["m.room.join_rules$"] = new MatrixEvent( @@ -125,7 +278,35 @@ describe("Room", function() { beforeEach(function() { stateLookup = {}; - room.currentState = mockRoomState; + room.currentState.getStateEvents.andCallFake(function(type, key) { + if (key === undefined) { + var prefix = type + "$"; + var list = []; + for (var stateBlob in stateLookup) { + if (!stateLookup.hasOwnProperty(stateBlob)) { continue; } + if (stateBlob.indexOf(prefix) === 0) { + list.push(stateLookup[stateBlob]); + } + } + return list; + } + else { + return stateLookup[type + "$" + key]; + } + }); + room.currentState.getMembers.andCallFake(function() { + var memberEvents = room.currentState.getStateEvents("m.room.member"); + var members = []; + for (var i = 0; i < memberEvents.length; i++) { + members.push({ + // not interested in user ID vs display name semantics. + // That should be tested in RoomMember UTs. + name: memberEvents[i].getSender(), + userId: memberEvents[i].getSender() + }); + } + return members; + }); }); it("should return the names of members in a private (invite join_rules)" +