Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
28dc6f6da0
8 changed files with 237 additions and 30 deletions
|
@ -470,7 +470,7 @@ export default React.createClass({
|
|||
GroupStore.registerListener(groupId, this.onGroupStoreUpdated.bind(this, firstInit));
|
||||
let willDoOnboarding = false;
|
||||
// XXX: This should be more fluxy - let's get the error from GroupStore .getError or something
|
||||
GroupStore.on('error', (err, errorGroupId) => {
|
||||
GroupStore.on('error', (err, errorGroupId, stateKey) => {
|
||||
if (this._unmounted || groupId !== errorGroupId) return;
|
||||
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN' && !willDoOnboarding) {
|
||||
dis.dispatch({
|
||||
|
@ -483,11 +483,13 @@ export default React.createClass({
|
|||
dis.dispatch({action: 'require_registration'});
|
||||
willDoOnboarding = true;
|
||||
}
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
editing: false,
|
||||
});
|
||||
if (stateKey === GroupStore.STATE_KEY.Summary) {
|
||||
this.setState({
|
||||
summary: null,
|
||||
error: err,
|
||||
editing: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -511,7 +513,6 @@ export default React.createClass({
|
|||
isUserMember: GroupStore.getGroupMembers(this.props.groupId).some(
|
||||
(m) => m.userId === this._matrixClient.credentials.userId,
|
||||
),
|
||||
error: null,
|
||||
});
|
||||
// XXX: This might not work but this.props.groupIsNew unused anyway
|
||||
if (this.props.groupIsNew && firstInit) {
|
||||
|
@ -1157,7 +1158,7 @@ export default React.createClass({
|
|||
|
||||
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
|
||||
return <Spinner />;
|
||||
} else if (this.state.summary) {
|
||||
} else if (this.state.summary && !this.state.error) {
|
||||
const summary = this.state.summary;
|
||||
|
||||
let avatarNode;
|
||||
|
|
|
@ -32,7 +32,9 @@ export default React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
members: null,
|
||||
membersError: null,
|
||||
invitedMembers: null,
|
||||
invitedMembersError: null,
|
||||
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
|
||||
};
|
||||
},
|
||||
|
@ -50,6 +52,19 @@ export default React.createClass({
|
|||
GroupStore.registerListener(groupId, () => {
|
||||
this._fetchMembers();
|
||||
});
|
||||
GroupStore.on('error', (err, errorGroupId, stateKey) => {
|
||||
if (this._unmounted || groupId !== errorGroupId) return;
|
||||
if (stateKey === GroupStore.STATE_KEY.GroupMembers) {
|
||||
this.setState({
|
||||
membersError: err,
|
||||
});
|
||||
}
|
||||
if (stateKey === GroupStore.STATE_KEY.GroupInvitedMembers) {
|
||||
this.setState({
|
||||
invitedMembersError: err,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_fetchMembers: function() {
|
||||
|
@ -83,7 +98,11 @@ export default React.createClass({
|
|||
this.setState({ searchQuery: ev.target.value });
|
||||
},
|
||||
|
||||
makeGroupMemberTiles: function(query, memberList) {
|
||||
makeGroupMemberTiles: function(query, memberList, memberListError) {
|
||||
if (memberListError) {
|
||||
return <div className="warning">{ _t("Failed to load group members") }</div>;
|
||||
}
|
||||
|
||||
const GroupMemberTile = sdk.getComponent("groups.GroupMemberTile");
|
||||
const TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||
query = (query || "").toLowerCase();
|
||||
|
@ -153,15 +172,26 @@ export default React.createClass({
|
|||
);
|
||||
|
||||
const joined = this.state.members ? <div className="mx_MemberList_joined">
|
||||
{ this.makeGroupMemberTiles(this.state.searchQuery, this.state.members) }
|
||||
{
|
||||
this.makeGroupMemberTiles(
|
||||
this.state.searchQuery,
|
||||
this.state.members,
|
||||
this.state.membersError,
|
||||
)
|
||||
}
|
||||
</div> : <div />;
|
||||
|
||||
const invited = (this.state.invitedMembers && this.state.invitedMembers.length > 0) ?
|
||||
<div className="mx_MemberList_invited">
|
||||
<h2>{ _t("Invited") }</h2>
|
||||
{ this.makeGroupMemberTiles(this.state.searchQuery, this.state.invitedMembers) }
|
||||
<h2>{_t("Invited")}</h2>
|
||||
{
|
||||
this.makeGroupMemberTiles(
|
||||
this.state.searchQuery,
|
||||
this.state.invitedMembers,
|
||||
this.state.invitedMembersError,
|
||||
)
|
||||
}
|
||||
</div> : <div />;
|
||||
|
||||
return (
|
||||
<div className="mx_MemberList">
|
||||
{ inputBox }
|
||||
|
|
|
@ -1099,6 +1099,7 @@
|
|||
"Community %(groupId)s not found": "Community %(groupId)s not found",
|
||||
"This Home server does not support communities": "This Home server does not support communities",
|
||||
"Failed to load %(groupId)s": "Failed to load %(groupId)s",
|
||||
"Failed to load group members": "Failed to load group members",
|
||||
"Couldn't load home page": "Couldn't load home page",
|
||||
"You are currently using Riot anonymously as a guest.": "You are currently using Riot anonymously as a guest.",
|
||||
"If you would like to create a Matrix account you can <a>register</a> now.": "If you would like to create a Matrix account you can <a>register</a> now.",
|
||||
|
|
|
@ -134,6 +134,8 @@
|
|||
"Failed to join room": "Failed to join room",
|
||||
"Failed to kick": "Failed to kick",
|
||||
"Failed to leave room": "Failed to leave room",
|
||||
"Failed to load %(groupId)s": "Failed to load %(groupId)s",
|
||||
"Failed to load group members": "Failed to load group members",
|
||||
"Failed to load timeline position": "Failed to load timeline position",
|
||||
"Failed to mute user": "Failed to mute user",
|
||||
"Failed to reject invite": "Failed to reject invite",
|
||||
|
|
|
@ -122,10 +122,6 @@ class GroupStore extends EventEmitter {
|
|||
);
|
||||
},
|
||||
};
|
||||
|
||||
this.on('error', (err, groupId) => {
|
||||
console.error(`GroupStore encountered error whilst fetching data for ${groupId}`, err);
|
||||
});
|
||||
}
|
||||
|
||||
_fetchResource(stateKey, groupId) {
|
||||
|
@ -148,7 +144,7 @@ class GroupStore extends EventEmitter {
|
|||
}
|
||||
|
||||
console.error(`Failed to get resource ${stateKey} for ${groupId}`, err);
|
||||
this.emit('error', err, groupId);
|
||||
this.emit('error', err, groupId, stateKey);
|
||||
}).finally(() => {
|
||||
// Indicate finished request, allow for future fetches
|
||||
delete this._fetchResourcePromise[stateKey][groupId];
|
||||
|
|
|
@ -164,7 +164,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should indicate failure after failed /summary', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_error');
|
||||
});
|
||||
|
||||
|
@ -179,7 +179,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should show a group avatar, name, id and short description after successful /summary', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView');
|
||||
|
||||
const avatar = ReactTestUtils.findRenderedComponentWithType(root, sdk.getComponent('avatars.GroupAvatar'));
|
||||
|
@ -214,7 +214,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should show a simple long description after successful /summary', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView');
|
||||
|
||||
const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc');
|
||||
|
@ -235,7 +235,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should show a placeholder if a long description is not set', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const placeholder = ReactTestUtils
|
||||
.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc_placeholder');
|
||||
const placeholderElement = ReactDOM.findDOMNode(placeholder);
|
||||
|
@ -255,7 +255,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should show a complicated long description after successful /summary', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc');
|
||||
const longDescElement = ReactDOM.findDOMNode(longDesc);
|
||||
expect(longDescElement).toExist();
|
||||
|
@ -282,7 +282,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should disallow images with non-mxc URLs', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const longDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_groupDesc');
|
||||
const longDescElement = ReactDOM.findDOMNode(longDesc);
|
||||
expect(longDescElement).toExist();
|
||||
|
@ -305,7 +305,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should show a RoomDetailList after a successful /summary & /rooms (no rooms returned)', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const roomDetailList = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_RoomDetailList');
|
||||
const roomDetailListElement = ReactDOM.findDOMNode(roomDetailList);
|
||||
expect(roomDetailListElement).toExist();
|
||||
|
@ -322,7 +322,7 @@ describe('GroupView', function() {
|
|||
|
||||
it('should show a RoomDetailList after a successful /summary & /rooms (with a single room)', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
const prom = waitForUpdate(groupView).then(() => {
|
||||
const prom = waitForUpdate(groupView, 4).then(() => {
|
||||
const roomDetailList = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_RoomDetailList');
|
||||
const roomDetailListElement = ReactDOM.findDOMNode(roomDetailList);
|
||||
expect(roomDetailListElement).toExist();
|
||||
|
@ -355,4 +355,25 @@ describe('GroupView', function() {
|
|||
httpBackend.flush(undefined, undefined, 0);
|
||||
return prom;
|
||||
});
|
||||
|
||||
it('should show a summary even if /users fails', function() {
|
||||
const groupView = ReactTestUtils.findRenderedComponentWithType(root, GroupView);
|
||||
|
||||
// Only wait for 3 updates in this test since we don't change state for
|
||||
// the /users error case.
|
||||
const prom = waitForUpdate(groupView, 3).then(() => {
|
||||
const shortDesc = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_GroupView_header_shortDesc');
|
||||
const shortDescElement = ReactDOM.findDOMNode(shortDesc);
|
||||
expect(shortDescElement).toExist();
|
||||
expect(shortDescElement.innerText).toBe('This is a community');
|
||||
});
|
||||
|
||||
httpBackend.when('GET', '/groups/' + groupIdEncoded + '/summary').respond(200, summaryResponse);
|
||||
httpBackend.when('GET', '/groups/' + groupIdEncoded + '/users').respond(500, {});
|
||||
httpBackend.when('GET', '/groups/' + groupIdEncoded + '/invited_users').respond(200, { chunk: [] });
|
||||
httpBackend.when('GET', '/groups/' + groupIdEncoded + '/rooms').respond(200, { chunk: [] });
|
||||
|
||||
httpBackend.flush(undefined, undefined, 0);
|
||||
return prom;
|
||||
});
|
||||
});
|
||||
|
|
149
test/components/views/groups/GroupMemberList-test.js
Normal file
149
test/components/views/groups/GroupMemberList-test.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import ReactTestUtils from "react-dom/test-utils";
|
||||
import expect from "expect";
|
||||
|
||||
import MockHttpBackend from "matrix-mock-request";
|
||||
import MatrixClientPeg from "../../../../src/MatrixClientPeg";
|
||||
import sdk from "matrix-react-sdk";
|
||||
import Matrix from "matrix-js-sdk";
|
||||
|
||||
import * as TestUtils from "test-utils";
|
||||
const { waitForUpdate } = TestUtils;
|
||||
|
||||
const GroupMemberList = sdk.getComponent("views.groups.GroupMemberList");
|
||||
const WrappedGroupMemberList = TestUtils.wrapInMatrixClientContext(GroupMemberList);
|
||||
|
||||
describe("GroupMemberList", function() {
|
||||
let root;
|
||||
let rootElement;
|
||||
let httpBackend;
|
||||
let summaryResponse;
|
||||
let groupId;
|
||||
let groupIdEncoded;
|
||||
|
||||
// Summary response fields
|
||||
const user = {
|
||||
is_privileged: true, // can edit the group
|
||||
is_public: true, // appear as a member to non-members
|
||||
is_publicised: true, // display flair
|
||||
};
|
||||
const usersSection = {
|
||||
roles: {},
|
||||
total_user_count_estimate: 0,
|
||||
users: [],
|
||||
};
|
||||
const roomsSection = {
|
||||
categories: {},
|
||||
rooms: [],
|
||||
total_room_count_estimate: 0,
|
||||
};
|
||||
|
||||
// Users response fields
|
||||
const usersResponse = {
|
||||
chunk: [
|
||||
{
|
||||
user_id: "@test:matrix.org",
|
||||
displayname: "Test",
|
||||
avatar_url: "mxc://matrix.org/oUxxDyzQOHdVDMxgwFzyCWEe",
|
||||
is_public: true,
|
||||
is_privileged: true,
|
||||
attestation: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
TestUtils.beforeEach(this);
|
||||
|
||||
httpBackend = new MockHttpBackend();
|
||||
|
||||
Matrix.request(httpBackend.requestFn);
|
||||
|
||||
MatrixClientPeg.get = () => Matrix.createClient({
|
||||
baseUrl: "https://my.home.server",
|
||||
userId: "@me:here",
|
||||
accessToken: "123456789",
|
||||
});
|
||||
|
||||
summaryResponse = {
|
||||
profile: {
|
||||
avatar_url: "mxc://someavatarurl",
|
||||
is_openly_joinable: true,
|
||||
is_public: true,
|
||||
long_description: "This is a <b>LONG</b> description.",
|
||||
name: "The name of a community",
|
||||
short_description: "This is a community",
|
||||
},
|
||||
user,
|
||||
users_section: usersSection,
|
||||
rooms_section: roomsSection,
|
||||
};
|
||||
|
||||
groupId = "+" + Math.random().toString(16).slice(2) + ":domain";
|
||||
groupIdEncoded = encodeURIComponent(groupId);
|
||||
|
||||
rootElement = document.createElement("div");
|
||||
root = ReactDOM.render(<WrappedGroupMemberList groupId={groupId} />, rootElement);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
ReactDOM.unmountComponentAtNode(rootElement);
|
||||
});
|
||||
|
||||
it("should show group member list after successful /users", function() {
|
||||
const groupMemberList = ReactTestUtils.findRenderedComponentWithType(root, GroupMemberList);
|
||||
const prom = waitForUpdate(groupMemberList, 4).then(() => {
|
||||
ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList");
|
||||
|
||||
const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined");
|
||||
const memberListElement = ReactDOM.findDOMNode(memberList);
|
||||
expect(memberListElement).toExist();
|
||||
expect(memberListElement.innerText).toBe("Test");
|
||||
});
|
||||
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/summary").respond(200, summaryResponse);
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/users").respond(200, usersResponse);
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/invited_users").respond(200, { chunk: [] });
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/rooms").respond(200, { chunk: [] });
|
||||
|
||||
httpBackend.flush(undefined, undefined, 0);
|
||||
return prom;
|
||||
});
|
||||
|
||||
it("should show error message after failed /users", function() {
|
||||
const groupMemberList = ReactTestUtils.findRenderedComponentWithType(root, GroupMemberList);
|
||||
const prom = waitForUpdate(groupMemberList, 4).then(() => {
|
||||
ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList");
|
||||
|
||||
const memberList = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_MemberList_joined");
|
||||
const memberListElement = ReactDOM.findDOMNode(memberList);
|
||||
expect(memberListElement).toExist();
|
||||
expect(memberListElement.innerText).toBe("Failed to load group members");
|
||||
});
|
||||
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/summary").respond(200, summaryResponse);
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/users").respond(500, {});
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/invited_users").respond(200, { chunk: [] });
|
||||
httpBackend.when("GET", "/groups/" + groupIdEncoded + "/rooms").respond(200, { chunk: [] });
|
||||
|
||||
httpBackend.flush(undefined, undefined, 0);
|
||||
return prom;
|
||||
});
|
||||
});
|
|
@ -310,19 +310,26 @@ export function wrapInMatrixClientContext(WrappedComponent) {
|
|||
/**
|
||||
* Call fn before calling componentDidUpdate on a react component instance, inst.
|
||||
* @param {React.Component} inst an instance of a React component.
|
||||
* @param {integer} updates Number of updates to wait for. (Defaults to 1.)
|
||||
* @returns {Promise} promise that resolves when componentDidUpdate is called on
|
||||
* given component instance.
|
||||
*/
|
||||
export function waitForUpdate(inst) {
|
||||
export function waitForUpdate(inst, updates = 1) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cdu = inst.componentDidUpdate;
|
||||
|
||||
console.log(`Waiting for ${updates} update(s)`);
|
||||
|
||||
inst.componentDidUpdate = (prevProps, prevState, snapshot) => {
|
||||
resolve();
|
||||
updates--;
|
||||
console.log(`Got update, ${updates} remaining`);
|
||||
|
||||
if (updates == 0) {
|
||||
inst.componentDidUpdate = cdu;
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (cdu) cdu(prevProps, prevState, snapshot);
|
||||
|
||||
inst.componentDidUpdate = cdu;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue