Fix invite dialog showing the same user multiple times (#11308)
* Fix invite dialog showing the same user multiple times * Add test * Improve coveragepull/28788/head^2
parent
b6e373c65b
commit
a70fcfd0bc
|
@ -21,6 +21,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { uniqBy } from "lodash";
|
||||||
|
|
||||||
import { Icon as InfoIcon } from "../../../../res/img/element-icons/info.svg";
|
import { Icon as InfoIcon } from "../../../../res/img/element-icons/info.svg";
|
||||||
import { Icon as EmailPillAvatarIcon } from "../../../../res/img/icon-email-pill-avatar.svg";
|
import { Icon as EmailPillAvatarIcon } from "../../../../res/img/icon-email-pill-avatar.svg";
|
||||||
|
@ -384,6 +385,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
this.state = {
|
this.state = {
|
||||||
targets: [], // array of Member objects (see interface above)
|
targets: [], // array of Member objects (see interface above)
|
||||||
filterText: this.props.initialText || "",
|
filterText: this.props.initialText || "",
|
||||||
|
// Mutates alreadyInvited set so that buildSuggestions doesn't duplicate any users
|
||||||
recents: InviteDialog.buildRecents(alreadyInvited),
|
recents: InviteDialog.buildRecents(alreadyInvited),
|
||||||
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
numRecentsShown: INITIAL_ROOMS_SHOWN,
|
||||||
suggestions: this.buildSuggestions(alreadyInvited),
|
suggestions: this.buildSuggestions(alreadyInvited),
|
||||||
|
@ -476,6 +478,8 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
}
|
}
|
||||||
|
|
||||||
recents.push({ userId, user: toMember(roomMember), lastActive: lastEventTs });
|
recents.push({ userId, user: toMember(roomMember), lastActive: lastEventTs });
|
||||||
|
// We mutate the given set so that any later callers avoid duplicating these users
|
||||||
|
excludedTargetIds.add(userId);
|
||||||
}
|
}
|
||||||
if (!recents) logger.warn("[Invite:Recents] No recents to suggest!");
|
if (!recents) logger.warn("[Invite:Recents] No recents to suggest!");
|
||||||
|
|
||||||
|
@ -821,7 +825,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
if (!this.state.busy) {
|
if (!this.state.busy) {
|
||||||
let filterText = this.state.filterText;
|
let filterText = this.state.filterText;
|
||||||
let targets = this.state.targets.map((t) => t); // cheap clone for mutation
|
let targets = this.state.targets.map((t) => t); // cheap clone for mutation
|
||||||
const idx = targets.indexOf(member);
|
const idx = targets.findIndex((m) => m.userId === member.userId);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
targets.splice(idx, 1);
|
targets.splice(idx, 1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -945,11 +949,11 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
if (unableToAddMore) {
|
if (unableToAddMore) {
|
||||||
this.setState({
|
this.setState({
|
||||||
filterText: unableToAddMore.join(" "),
|
filterText: unableToAddMore.join(" "),
|
||||||
targets: [...this.state.targets, ...toAdd],
|
targets: uniqBy([...this.state.targets, ...toAdd], (t) => t.userId),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
targets: [...this.state.targets, ...toAdd],
|
targets: uniqBy([...this.state.targets, ...toAdd], (t) => t.userId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1001,6 +1005,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
// The type of u is a pain to define but members of both mixins have the 'userId' property
|
// The type of u is a pain to define but members of both mixins have the 'userId' property
|
||||||
const notAlreadyExists = (u: any): boolean => {
|
const notAlreadyExists = (u: any): boolean => {
|
||||||
return (
|
return (
|
||||||
|
!this.state.recents.some((m) => m.userId === u.userId) &&
|
||||||
!sourceMembers.some((m) => m.userId === u.userId) &&
|
!sourceMembers.some((m) => m.userId === u.userId) &&
|
||||||
!priorityAdditionalMembers.some((m) => m.userId === u.userId) &&
|
!priorityAdditionalMembers.some((m) => m.userId === u.userId) &&
|
||||||
!otherAdditionalMembers.some((m) => m.userId === u.userId)
|
!otherAdditionalMembers.some((m) => m.userId === u.userId)
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { MatrixClient, MatrixError, Room } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, MatrixError, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
@ -366,6 +366,35 @@ describe("InviteDialog", () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not allow pasting the same user multiple times", async () => {
|
||||||
|
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||||
|
|
||||||
|
const input = screen.getByTestId("invite-dialog-input");
|
||||||
|
input.focus();
|
||||||
|
await userEvent.paste(`${bobId}`);
|
||||||
|
await userEvent.paste(`${bobId}`);
|
||||||
|
await userEvent.paste(`${bobId}`);
|
||||||
|
|
||||||
|
expect(input).toHaveValue("");
|
||||||
|
await expect(screen.findAllByText(bobId, { selector: "a" })).resolves.toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should add to selection on click of user tile", async () => {
|
||||||
|
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||||
|
|
||||||
|
const input = screen.getByTestId("invite-dialog-input");
|
||||||
|
input.focus();
|
||||||
|
await userEvent.keyboard(`${aliceId}`);
|
||||||
|
|
||||||
|
const btn = await screen.findByText(aliceId, {
|
||||||
|
selector: ".mx_InviteDialog_tile_nameStack_userId .mx_InviteDialog_tile--room_highlight",
|
||||||
|
});
|
||||||
|
fireEvent.click(btn);
|
||||||
|
|
||||||
|
const tile = await screen.findByText(aliceId, { selector: ".mx_InviteDialog_userTile_name" });
|
||||||
|
expect(tile).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
describe("when inviting a user with an unknown profile", () => {
|
describe("when inviting a user with an unknown profile", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||||
|
|
Loading…
Reference in New Issue