diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 14dfa91fa48..99841c986ec 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -175,4 +175,4 @@ class MatrixClientPeg { if (!global.mxMatrixClientPeg) { global.mxMatrixClientPeg = new MatrixClientPeg(); } -module.exports = global.mxMatrixClientPeg; +export default global.mxMatrixClientPeg; diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index f8443c6ecdd..d1f102d9fe4 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -42,6 +42,9 @@ module.exports = React.createClass({ // should the user be able to change the value? false by default. disabled: PropTypes.bool, onChange: PropTypes.func, + + // Optional key to pass as the second argument to `onChange` + powerLevelKey: PropTypes.string, }, getInitialState: function() { @@ -84,17 +87,17 @@ module.exports = React.createClass({ onSelectChange: function(event) { this.setState({ custom: event.target.value === "SELECT_VALUE_CUSTOM" }); if (event.target.value !== "SELECT_VALUE_CUSTOM") { - this.props.onChange(event.target.value); + this.props.onChange(event.target.value, this.props.powerLevelKey); } }, onCustomBlur: function(event) { - this.props.onChange(parseInt(this.refs.custom.value)); + this.props.onChange(parseInt(this.refs.custom.value), this.props.powerLevelKey); }, onCustomKeyDown: function(event) { if (event.key == "Enter") { - this.props.onChange(parseInt(this.refs.custom.value)); + this.props.onChange(parseInt(this.refs.custom.value), this.props.powerLevelKey); } }, diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index ea4a29615e0..2bd2fbb8f1a 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -117,7 +117,6 @@ module.exports = React.createClass({ propTypes: { room: PropTypes.object.isRequired, - onSaveClick: PropTypes.func, }, getInitialState: function() { @@ -151,7 +150,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().getRoomDirectoryVisibility( this.props.room.roomId, - ).done((result) => { + ).done((result = {}) => { this.setState({ isRoomPublished: result.visibility === "public" }); this._originalIsRoomPublished = result.visibility === "public"; }, (err) => { diff --git a/test/components/views/rooms/RoomSettings-test.js b/test/components/views/rooms/RoomSettings-test.js new file mode 100644 index 00000000000..12fb734bf43 --- /dev/null +++ b/test/components/views/rooms/RoomSettings-test.js @@ -0,0 +1,192 @@ +import React from 'react'; +import ReactTestUtils from 'react-addons-test-utils'; +import ReactDOM from 'react-dom'; +import expect, {createSpy} from 'expect'; +import sinon from 'sinon'; +import Promise from 'bluebird'; +import * as testUtils from '../../../test-utils'; +import sdk from 'matrix-react-sdk'; +const WrappedRoomSettings = testUtils.wrapInMatrixClientContext(sdk.getComponent('views.rooms.RoomSettings')); +import MatrixClientPeg from '../../../../src/MatrixClientPeg'; +import SettingsStore from '../../../../src/settings/SettingsStore'; + + +describe('RoomSettings', () => { + let parentDiv = null; + let sandbox = null; + let client = null; + let roomSettings = null; + const room = testUtils.mkStubRoom('!DdJkzRliezrwpNebLk:matrix.org'); + + function expectSentStateEvent(roomId, eventType, expectedEventContent) { + let found = false; + for (const call of client.sendStateEvent.calls) { + const [ + actualRoomId, + actualEventType, + actualEventContent, + ] = call.arguments.slice(0, 3); + + if (roomId === actualRoomId && actualEventType === eventType) { + expect(actualEventContent).toEqual(expectedEventContent); + found = true; + break; + } + } + expect(found).toBe(true); + } + + beforeEach(function(done) { + testUtils.beforeEach(this); + sandbox = testUtils.stubClient(); + client = MatrixClientPeg.get(); + client.credentials = {userId: '@me:domain.com'}; + + client.setRoomName = createSpy().andReturn(Promise.resolve()); + client.setRoomTopic = createSpy().andReturn(Promise.resolve()); + client.setRoomDirectoryVisibility = createSpy().andReturn(Promise.resolve()); + + // Covers any room state event (e.g. name, avatar, topic) + client.sendStateEvent = createSpy().andReturn(Promise.resolve()); + + // Covers room tagging + client.setRoomTag = createSpy().andReturn(Promise.resolve()); + client.deleteRoomTag = createSpy().andReturn(Promise.resolve()); + + // Covers any setting in the SettingsStore + // (including local client settings not stored via matrix) + SettingsStore.setValue = createSpy().andReturn(Promise.resolve()); + + parentDiv = document.createElement('div'); + document.body.appendChild(parentDiv); + + const gatherWrappedRef = (r) => {roomSettings = r;}; + + // get use wrappedRef because we're using wrapInMatrixClientContext + ReactDOM.render( + , + parentDiv, + done, + ); + }); + + afterEach((done) => { + if (parentDiv) { + ReactDOM.unmountComponentAtNode(parentDiv); + parentDiv.remove(); + parentDiv = null; + } + sandbox.restore(); + done(); + }); + + it('should not set when no setting is changed', (done) => { + roomSettings.save().then(() => { + expect(client.sendStateEvent).toNotHaveBeenCalled(); + expect(client.setRoomTag).toNotHaveBeenCalled(); + expect(client.deleteRoomTag).toNotHaveBeenCalled(); + done(); + }); + }); + + // XXX: Apparently we do call SettingsStore.setValue + xit('should not settings via the SettingsStore when no setting is changed', (done) => { + roomSettings.save().then(() => { + expect(SettingsStore.setValue).toNotHaveBeenCalled(); + done(); + }); + }); + + it('should set room name when it has changed', (done) => { + const name = "My Room Name"; + roomSettings.setName(name); + + roomSettings.save().then(() => { + expect(client.setRoomName.calls[0].arguments.slice(0, 2)) + .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', name]); + + done(); + }); + }); + + it('should set room topic when it has changed', (done) => { + const topic = "this is a topic"; + roomSettings.setTopic(topic); + + roomSettings.save().then(() => { + expect(client.setRoomTopic.calls[0].arguments.slice(0, 2)) + .toEqual(['!DdJkzRliezrwpNebLk:matrix.org', topic]); + + done(); + }); + }); + + it('should set history visibility when it has changed', (done) => { + const historyVisibility = "translucent"; + roomSettings.setState({ + history_visibility: historyVisibility, + }); + + roomSettings.save().then(() => { + expectSentStateEvent( + "!DdJkzRliezrwpNebLk:matrix.org", + "m.room.history_visibility", {history_visibility: historyVisibility}, + ); + done(); + }); + }); + + // XXX: Can't test this because we `getRoomDirectoryVisibility` in `componentWillMount` + xit('should set room directory publicity when set to true', (done) => { + const isRoomPublished = true; + roomSettings.setState({ + isRoomPublished, + }, () => { + roomSettings.save().then(() => { + expect(client.setRoomDirectoryVisibility.calls[0].arguments.slice(0, 2)) + .toEqual("!DdJkzRliezrwpNebLk:matrix.org", isRoomPublished ? "public" : "private"); + done(); + }); + }); + }); + + it('should set power levels when changed', (done) => { + roomSettings.onPowerLevelsChanged(42, "invite"); + + roomSettings.save().then(() => { + expectSentStateEvent( + "!DdJkzRliezrwpNebLk:matrix.org", + "m.room.power_levels", { invite: 42 }, + ); + done(); + }); + }); + + it('should set event power levels when changed', (done) => { + roomSettings.onPowerLevelsChanged(42, "event_levels_m.room.message"); + + roomSettings.save().then(() => { + // We expect all state events to be set to the state_default (50) + // See powerLevelDescriptors in RoomSettings + expectSentStateEvent( + "!DdJkzRliezrwpNebLk:matrix.org", + "m.room.power_levels", { + events: { + 'm.room.message': 42, + 'm.room.avatar': 50, + 'm.room.name': 50, + 'm.room.canonical_alias': 50, + 'm.room.history_visibility': 50, + 'm.room.power_levels': 50, + 'm.room.topic': 50, + 'im.vector.modular.widgets': 50, + }, + }, + ); + done(); + }); + }); +}); diff --git a/test/test-utils.js b/test/test-utils.js index 5753c02665e..b593761bd46 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -68,6 +68,8 @@ export function createTestClient() { return { getHomeserverUrl: sinon.stub(), getIdentityServerUrl: sinon.stub(), + getDomain: sinon.stub().returns("matrix.rog"), + getUserId: sinon.stub().returns("@userId:matrix.rog"), getPushActionsForEvent: sinon.stub(), getRoom: sinon.stub().returns(mkStubRoom()), @@ -81,6 +83,7 @@ export function createTestClient() { paginateEventTimeline: sinon.stub().returns(Promise.resolve()), sendReadReceipt: sinon.stub().returns(Promise.resolve()), getRoomIdForAlias: sinon.stub().returns(Promise.resolve()), + getRoomDirectoryVisibility: sinon.stub().returns(Promise.resolve()), getProfileInfo: sinon.stub().returns(Promise.resolve({})), getAccountData: (type) => { return mkEvent({ @@ -244,6 +247,7 @@ export function mkStubRoom(roomId = null) { roomId: roomId, getAvatarUrl: () => 'mxc://avatar.url/image.png', }), + getMembersWithMembership: sinon.stub().returns([]), getJoinedMembers: sinon.stub().returns([]), getPendingEvents: () => [], getLiveTimeline: () => stubTimeline, @@ -252,8 +256,16 @@ export function mkStubRoom(roomId = null) { hasMembershipState: () => null, currentState: { getStateEvents: sinon.stub(), + mayClientSendStateEvent: sinon.stub().returns(true), + maySendStateEvent: sinon.stub().returns(true), members: [], }, + tags: { + "m.favourite": { + order: 0.5, + }, + }, + setBlacklistUnverifiedDevices: sinon.stub(), }; } @@ -284,7 +296,7 @@ export function wrapInMatrixClientContext(WrappedComponent) { } render() { - return ; + return ; } } return Wrapper;