[MM-31547] Stop users from being able to enter the same server name or URL twice (#2049)

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Devin Binnie
2022-04-19 09:31:04 -04:00
committed by GitHub
parent a26e3caf23
commit ce2ddb6a6a
6 changed files with 72 additions and 8 deletions

View File

@@ -64,6 +64,22 @@ describe('Add Server Modal', function desc() {
existing.should.be.true; existing.should.be.true;
}); });
it('should not be valid if a server with the same name exists', async () => {
await newServerView.type('#teamNameInput', config.teams[0].name);
await newServerView.type('#teamUrlInput', 'http://example.org');
await newServerView.click('#saveNewServerModal');
const existing = await newServerView.isVisible('#teamNameInput.is-invalid');
existing.should.be.true;
});
it('should not be valid if a server with the same URL exists', async () => {
await newServerView.type('#teamNameInput', 'some-new-server');
await newServerView.type('#teamUrlInput', config.teams[0].url);
await newServerView.click('#saveNewServerModal');
const existing = await newServerView.isVisible('#teamUrlInput.is-invalid');
existing.should.be.true;
});
describe('Valid server name', async () => { describe('Valid server name', async () => {
beforeEach(async () => { beforeEach(async () => {
await newServerView.type('#teamNameInput', 'TestTeam'); await newServerView.type('#teamNameInput', 'TestTeam');

View File

@@ -109,6 +109,18 @@ describe('EditServerModal', function desc() {
existing.should.be.true; existing.should.be.true;
}); });
it('should not edit team if another server with the same name or URL exists', async () => {
await editServerView.fill('#teamNameInput', config.teams[1].name);
await editServerView.click('#saveNewServerModal');
let existing = await editServerView.isVisible('#teamNameInput.is-invalid');
existing.should.be.true;
await editServerView.fill('#teamNameInput', 'NewTestTeam');
await editServerView.fill('#teamUrlInput', config.teams[1].url);
existing = await editServerView.isVisible('#teamUrlInput.is-invalid');
existing.should.be.true;
});
it('MM-T4391_2 should edit team when Save is pressed and name edited', async () => { it('MM-T4391_2 should edit team when Save is pressed and name edited', async () => {
await editServerView.fill('#teamNameInput', 'NewTestTeam'); await editServerView.fill('#teamNameInput', 'NewTestTeam');
await editServerView.click('#saveNewServerModal'); await editServerView.click('#saveNewServerModal');

View File

@@ -4,7 +4,7 @@
import {app, dialog, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron'; import {app, dialog, IpcMainEvent, IpcMainInvokeEvent, Menu} from 'electron';
import log from 'electron-log'; import log from 'electron-log';
import {Team} from 'types/config'; import {Team, TeamWithIndex} from 'types/config';
import {MentionData} from 'types/notification'; import {MentionData} from 'types/notification';
import Config from 'common/config'; import Config from 'common/config';
@@ -109,7 +109,7 @@ export function handleNewServerModal() {
if (!mainWindow) { if (!mainWindow) {
return; return;
} }
const modalPromise = ModalManager.addModal<unknown, Team>('newServer', html, modalPreload, {}, mainWindow, Config.teams.length === 0); const modalPromise = ModalManager.addModal<TeamWithIndex[], Team>('newServer', html, modalPreload, Config.teams.map((team, index) => ({...team, index})), mainWindow, Config.teams.length === 0);
if (modalPromise) { if (modalPromise) {
modalPromise.then((data) => { modalPromise.then((data) => {
const teams = Config.teams; const teams = Config.teams;
@@ -145,7 +145,15 @@ export function handleEditServerModal(e: IpcMainEvent, name: string) {
if (serverIndex < 0) { if (serverIndex < 0) {
return; return;
} }
const modalPromise = ModalManager.addModal<Team, Team>('editServer', html, modalPreload, Config.teams[serverIndex], mainWindow); const modalPromise = ModalManager.addModal<{currentTeams: TeamWithIndex[]; team: TeamWithIndex}, Team>(
'editServer',
html,
modalPreload,
{
currentTeams: Config.teams.map((team, index) => ({...team, index})),
team: {...Config.teams[serverIndex], index: serverIndex},
},
mainWindow);
if (modalPromise) { if (modalPromise) {
modalPromise.then((data) => { modalPromise.then((data) => {
const teams = Config.teams; const teams = Config.teams;

View File

@@ -13,6 +13,7 @@ type Props = {
onClose?: () => void; onClose?: () => void;
onSave?: (team: TeamWithIndex) => void; onSave?: (team: TeamWithIndex) => void;
team?: TeamWithIndex; team?: TeamWithIndex;
currentTeams?: TeamWithIndex[];
editMode?: boolean; editMode?: boolean;
show?: boolean; show?: boolean;
restoreFocus?: boolean; restoreFocus?: boolean;
@@ -62,6 +63,15 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
if (!this.state.saveStarted) { if (!this.state.saveStarted) {
return null; return null;
} }
if (this.props.currentTeams) {
const currentTeams = [...this.props.currentTeams];
if (this.props.editMode && this.props.team) {
currentTeams.splice(this.props.team.index, 1);
}
if (currentTeams.find((team) => team.name === this.state.teamName)) {
return 'A server with the same name already exists.';
}
}
return this.state.teamName.length > 0 ? null : 'Name is required.'; return this.state.teamName.length > 0 ? null : 'Name is required.';
} }
@@ -79,6 +89,15 @@ export default class NewTeamModal extends React.PureComponent<Props, State> {
if (!this.state.saveStarted) { if (!this.state.saveStarted) {
return null; return null;
} }
if (this.props.currentTeams) {
const currentTeams = [...this.props.currentTeams];
if (this.props.editMode && this.props.team) {
currentTeams.splice(this.props.team.index, 1);
}
if (currentTeams.find((team) => team.url === this.state.teamUrl)) {
return 'A server with the same URL already exists.';
}
}
if (this.state.teamUrl.length === 0) { if (this.state.teamUrl.length === 0) {
return 'URL is required.'; return 'URL is required.';
} }

View File

@@ -28,11 +28,13 @@ const onSave = (data: TeamWithIndex) => {
const EditServerModalWrapper: React.FC = () => { const EditServerModalWrapper: React.FC = () => {
const [server, setServer] = useState<TeamWithIndex>(); const [server, setServer] = useState<TeamWithIndex>();
const [currentTeams, setCurrentTeams] = useState<TeamWithIndex[]>();
const handleEditServerMessage = (event: {data: ModalMessage<TeamWithIndex>}) => { const handleEditServerMessage = (event: {data: ModalMessage<{currentTeams: TeamWithIndex[]; team: TeamWithIndex}>}) => {
switch (event.data.type) { switch (event.data.type) {
case MODAL_INFO: { case MODAL_INFO: {
setServer(event.data.data); setServer(event.data.data.team);
setCurrentTeams(event.data.data.currentTeams);
break; break;
} }
default: default:
@@ -52,6 +54,7 @@ const EditServerModalWrapper: React.FC = () => {
editMode={true} editMode={true}
show={Boolean(server)} show={Boolean(server)}
team={server} team={server}
currentTeams={currentTeams}
/> />
); );
}; };

View File

@@ -10,7 +10,7 @@ import ReactDOM from 'react-dom';
import {TeamWithIndex} from 'types/config'; import {TeamWithIndex} from 'types/config';
import {ModalMessage} from 'types/modals'; import {ModalMessage} from 'types/modals';
import {GET_MODAL_UNCLOSEABLE, MODAL_CANCEL, MODAL_RESULT, MODAL_UNCLOSEABLE} from 'common/communication'; import {GET_MODAL_UNCLOSEABLE, MODAL_CANCEL, MODAL_INFO, MODAL_RESULT, MODAL_UNCLOSEABLE, RETRIEVE_MODAL_INFO} from 'common/communication';
import NewTeamModal from '../../components/NewTeamModal'; //'./addServer.jsx'; import NewTeamModal from '../../components/NewTeamModal'; //'./addServer.jsx';
@@ -28,13 +28,17 @@ const onSave = (data: TeamWithIndex) => {
const NewServerModalWrapper: React.FC = () => { const NewServerModalWrapper: React.FC = () => {
const [unremoveable, setUnremovable] = useState<boolean>(); const [unremoveable, setUnremovable] = useState<boolean>();
const [currentTeams, setCurrentTeams] = useState<TeamWithIndex[]>();
const handleNewServerMessage = (event: {data: ModalMessage<boolean>}) => { const handleNewServerMessage = (event: {data: ModalMessage<unknown>}) => {
switch (event.data.type) { switch (event.data.type) {
case MODAL_UNCLOSEABLE: { case MODAL_UNCLOSEABLE: {
setUnremovable(event.data.data); setUnremovable(event.data.data as boolean);
break; break;
} }
case MODAL_INFO:
setCurrentTeams(event.data.data as TeamWithIndex[]);
break;
default: default:
break; break;
} }
@@ -43,6 +47,7 @@ const NewServerModalWrapper: React.FC = () => {
useEffect(() => { useEffect(() => {
window.addEventListener('message', handleNewServerMessage); window.addEventListener('message', handleNewServerMessage);
window.postMessage({type: GET_MODAL_UNCLOSEABLE}, window.location.href); window.postMessage({type: GET_MODAL_UNCLOSEABLE}, window.location.href);
window.postMessage({type: RETRIEVE_MODAL_INFO}, window.location.href);
return () => { return () => {
window.removeEventListener('message', handleNewServerMessage); window.removeEventListener('message', handleNewServerMessage);
@@ -55,6 +60,7 @@ const NewServerModalWrapper: React.FC = () => {
onSave={onSave} onSave={onSave}
editMode={false} editMode={false}
show={true} show={true}
currentTeams={currentTeams}
/> />
); );
}; };