import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { isRoomFederated } from '@rocket.chat/core-typings';
import { Box, Icon } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { escapeHTML } from '@rocket.chat/string-helpers';
import { usePermission, useSetModal, useTranslation, useUser, useUserRoom, useUserSubscription } from '@rocket.chat/ui-contexts';
import React, { useMemo } from 'react';

import GenericModal from '../../../../../components/GenericModal';
import { useEndpointAction } from '../../../../../hooks/useEndpointAction';
import * as Federation from '../../../../../lib/federation/Federation';
import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';
import type { Action } from '../../../../hooks/useActionSpread';
import RemoveUsersModal from '../../../../teams/contextualBar/members/RemoveUsersModal';
import { getRoomDirectives } from '../../../lib/getRoomDirectives';

// TODO: Remove endpoint concatenation
export const useRemoveUserAction = (user: Pick<IUser, '_id' | 'username'>, rid: IRoom['_id'], reload?: () => void): Action | undefined => {
	const t = useTranslation();
	const room = useUserRoom(rid);
	const currentUser = useUser();
	const subscription = useUserSubscription(rid);

	const { _id: uid } = user;

	if (!room) {
		throw Error('Room not provided');
	}

	const hasPermissionToRemove = usePermission('remove-user', rid);
	const userCanRemove = isRoomFederated(room)
		? Federation.isEditableByTheUser(currentUser || undefined, room, subscription)
		: hasPermissionToRemove;
	const setModal = useSetModal();
	const closeModal = useMutableCallback(() => setModal(null));
	const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room));

	const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels';
	const { roomCanRemove } = getRoomDirectives({ room, showingUserId: uid, userSubscription: subscription });

	const removeFromTeam = useEndpointAction('POST', '/v1/teams.removeMember', {
		successMessage: t('User_has_been_removed_from_team'),
	});
	const removeFromRoom = useEndpointAction('POST', `${endpointPrefix}.kick`, {
		successMessage: t('User_has_been_removed_from_s', roomName),
	});

	const removeUserOptionAction = useMutableCallback(() => {
		const handleRemoveFromTeam = async (rooms: IRoom[]): Promise<void> => {
			if (room.teamId) {
				const roomKeys = Object.keys(rooms);
				await removeFromTeam({
					teamId: room.teamId,
					userId: uid,
					...(roomKeys.length && { rooms: roomKeys }),
				});
				closeModal();
				reload?.();
			}
		};

		const handleRemoveFromRoom = async (rid: IRoom['_id'], uid: IUser['_id']): Promise<void> => {
			await removeFromRoom({ roomId: rid, userId: uid });
			closeModal();
			reload?.();
		};

		if (room.teamMain && room.teamId) {
			return setModal(
				<RemoveUsersModal teamId={room?.teamId} userId={uid} onClose={closeModal} onCancel={closeModal} onConfirm={handleRemoveFromTeam} />,
			);
		}

		setModal(
			<GenericModal
				variant='danger'
				confirmText={t('Yes_remove_user')}
				onClose={closeModal}
				onCancel={closeModal}
				onConfirm={(): Promise<void> => handleRemoveFromRoom(rid, uid)}
			>
				{t('The_user_will_be_removed_from_s', roomName)}
			</GenericModal>,
		);
	});

	const removeUserOption = useMemo(
		() =>
			roomCanRemove && userCanRemove
				? {
						label: (
							<Box color='status-font-on-danger'>
								<Icon mie='x4' name='cross' size='x20' />
								{room?.teamMain ? t('Remove_from_team') : t('Remove_from_room')}
							</Box>
						),
						action: removeUserOptionAction,
				  }
				: undefined,
		[room, roomCanRemove, userCanRemove, removeUserOptionAction, t],
	);

	return removeUserOption;
};


it('should be able to get the file if member and setting blocks outside access', async () => {

it('should not be able to get the file without credentials', async () => {

import { SlackImporter } from './importer';
import { Importers } from '../../importer/server';
import { SlackImporterInfo } from '../lib/info';

Importers.add(new SlackImporterInfo(), SlackImporter);


import { Box, Pagination, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, RefObject } from 'react';
import React, { useMemo, useCallback } from 'react';

import GenericModal from '../../../../components/GenericModal';
import {
	GenericTable,
	GenericTableHeader,
	GenericTableBody,
	GenericTableLoadingTable,
	GenericTableHeaderCell,
} from '../../../../components/GenericTable';
import { usePagination } from '../../../../components/GenericTable/hooks/usePagination';
import { useEndpointData } from '../../../../hooks/useEndpointData';
import { useResizeInlineBreakpoint } from '../../../../hooks/useResizeInlineBreakpoint';
import { AsyncStatePhase } from '../../../../lib/asyncState';
import AccountTokensRow from './AccountTokensRow';
import AddToken from './AddToken';

const AccountTokensTable = (): ReactElement => {
	const t = useTranslation();
	const dispatchToastMessage = useToastMessageDispatch();
	const setModal = useSetModal();
	const userId = useUserId();

	const regenerateToken = useMethod('personalAccessTokens:regenerateToken');
	const removeToken = useMethod('personalAccessTokens:removeToken');
	const { value: data, phase, error, reload } = useEndpointData('/v1/users.getPersonalAccessTokens');

	const [ref, isMedium] = useResizeInlineBreakpoint([600], 200) as [RefObject<HTMLElement>, boolean];

	const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination();

	const filteredTokens = useMemo(() => {
		if (!data?.tokens) {
			return null;
		}

		const sliceStart = current > data?.tokens.length ? data?.tokens.length - itemsPerPage : current;
		return data?.tokens.slice(sliceStart, sliceStart + itemsPerPage);
	}, [current, data?.tokens, itemsPerPage]);

	const closeModal = useCallback(() => setModal(null), [setModal]);

	const headers = useMemo(
		() =>
			[
				<GenericTableHeaderCell key={'name'}>{t('API_Personal_Access_Token_Name')}</GenericTableHeaderCell>,
				isMedium && <GenericTableHeaderCell key={'createdAt'}>{t('Created_at')}</GenericTableHeaderCell>,
				<GenericTableHeaderCell key={'lastTokenPart'}>{t('Last_token_part')}</GenericTableHeaderCell>,
				<GenericTableHeaderCell key={'2fa'}>{t('Two Factor Authentication')}</GenericTableHeaderCell>,
				<GenericTableHeaderCell key={'actions'} />,
			].filter(Boolean),
		[isMedium, t],
	);

	const handleRegenerate = useCallback(
		(name) => {
			const onConfirm: () => Promise<void> = async () => {
				try {
					setModal(null);
					const token = await regenerateToken({ tokenName: name });

					setModal(
						<GenericModal title={t('API_Personal_Access_Token_Generated')} onConfirm={closeModal}>
							<Box
								dangerouslySetInnerHTML={{
									__html: t('API_Personal_Access_Token_Generated_Text_Token_s_UserId_s', {
										token,
										userId,
									}),
								}}
							/>
						</GenericModal>,
					);

					reload();
				} catch (error) {
					setModal(null);
					dispatchToastMessage({ type: 'error', message: error });
				}
			};

			setModal(
				<GenericModal
					variant='warning'
					confirmText={t('API_Personal_Access_Tokens_Regenerate_It')}
					onConfirm={onConfirm}
					onCancel={closeModal}
					onClose={closeModal}
				>
					{t('API_Personal_Access_Tokens_Regenerate_Modal')}
				</GenericModal>,
			);
		},
		[closeModal, dispatchToastMessage, regenerateToken, reload, setModal, t, userId],
	);

	const handleRemove = useCallback(
		(name) => {
			const onConfirm: () => Promise<void> = async () => {
				try {
					await removeToken({ tokenName: name });
					dispatchToastMessage({ type: 'success', message: t('Token_has_been_removed') });
					reload();
					closeModal();
				} catch (error) {
					dispatchToastMessage({ type: 'error', message: error });
				}
			};

			setModal(
				<GenericModal variant='danger' confirmText={t('Remove')} onConfirm={onConfirm} onCancel={closeModal} onClose={closeModal}>
					{t('API_Personal_Access_Tokens_Remove_Modal')}
				</GenericModal>,
			);
		},
		[closeModal, dispatchToastMessage, reload, removeToken, setModal, t],
	);

	if (phase === AsyncStatePhase.REJECTED) {
		return (
			<Box display='flex' justifyContent='center' alignItems='center' height='100%'>
				<States>
					<StatesIcon name='warning' variation='danger' />
					<StatesTitle>{t('Something_went_wrong')}</StatesTitle>
					<StatesSubtitle>{t('We_Could_not_retrive_any_data')}</StatesSubtitle>
					<StatesSubtitle>{error?.message}</StatesSubtitle>
					<StatesActions>
						<StatesAction onClick={reload}>{t('Retry')}</StatesAction>
					</StatesActions>
				</States>
			</Box>
		);
	}

	return (
		<>
			<AddToken reload={reload} />
			{phase === AsyncStatePhase.LOADING && (
				<GenericTable>
					<GenericTableHeader>{headers}</GenericTableHeader>
					<GenericTableBody>{phase === AsyncStatePhase.LOADING && <GenericTableLoadingTable headerCells={5} />}</GenericTableBody>
				</GenericTable>
			)}
			{filteredTokens && filteredTokens?.length > 0 && phase === AsyncStatePhase.RESOLVED && (
				<>
					<GenericTable ref={ref}>
						<GenericTableHeader>{headers}</GenericTableHeader>
						<GenericTableBody>
							{phase === AsyncStatePhase.RESOLVED &&
								filteredTokens &&
								filteredTokens.map((filteredToken) => (
									<AccountTokensRow
										{...filteredToken}
										key={filteredToken.createdAt}
										onRegenerate={handleRegenerate}
										onRemove={handleRemove}
										isMedium={isMedium}
									/>
								))}
						</GenericTableBody>
					</GenericTable>
					<Pagination
						divider
						current={current}
						itemsPerPage={itemsPerPage}
						count={data?.tokens.length || 0}
						onSetItemsPerPage={onSetItemsPerPage}
						onSetCurrent={onSetCurrent}
						{...paginationProps}
					/>
				</>
			)}
			{phase === AsyncStatePhase.RESOLVED && filteredTokens?.length === 0 && (
				<States>
					<StatesIcon name='magnifier' />
					<StatesTitle>{t('No_results_found')}</StatesTitle>
				</States>
			)}
		</>
	);
};

export default AccountTokensTable;


keep keep keep replace keep keep keep keep

import { expect } from 'chai';

import { getCredentials, api, request, credentials } from '../../data/api-data.js';
import { password } from '../../data/user';
import { closeRoom, createRoom } from '../../data/rooms.helper';
import { imgURL } from '../../data/interactions.js';
import { updateEEPermission, updatePermission, updateSetting } from '../../data/permissions.helper';
import { sendSimpleMessage } from '../../data/chat.helper';
import { createUser, deleteUser, login } from '../../data/users.helper';
import { IS_EE } from '../../e2e/config/constants';

describe('[Rooms]', function () {
	this.retries(0);

	before((done) => getCredentials(done));

	it('/rooms.get', (done) => {
		request
			.get(api('rooms.get'))
			.set(credentials)
			.expect(200)
			.expect((res) => {
				expect(res.body).to.have.property('success', true);
				expect(res.body).to.have.property('update');
				expect(res.body).to.have.property('remove');
			})
			.end(done);
	});

	it('/rooms.get?updatedSince', (done) => {
		request
			.get(api('rooms.get'))
			.set(credentials)
			.query({
				updatedSince: new Date(),
			})
			.expect(200)
			.expect((res) => {
				expect(res.body).to.have.property('success', true);
				expect(res.body).to.have.property('update').that.have.lengthOf(0);
				expect(res.body).to.have.property('remove').that.have.lengthOf(0);
			})
			.end(done);
	});

	describe('/rooms.saveNotification:', () => {
		let testChannel;
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('/rooms.saveNotification:', (done) => {
			request
				.post(api('rooms.saveNotification'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
					notifications: {
						disableNotifications: '0',
						emailNotifications: 'nothing',
						audioNotificationValue: 'beep',
						desktopNotifications: 'nothing',
						mobilePushNotifications: 'mentions',
					},
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
	});

	describe('/rooms.upload', () => {
		let testChannel;
		let user;
		let userCredentials;

		before(async () => {
			user = await createUser({ joinDefaultChannels: false });
			userCredentials = await login(user.username, password);
		});

		after(async () => {
			await deleteUser(user);
			user = undefined;

			await updateSetting('FileUpload_Restrict_to_room_members', false);
			await updateSetting('FileUpload_ProtectFiles', true);
		});

		const testChannelName = `channel.test.upload.${Date.now()}-${Math.random()}`;

		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it("don't upload a file to room with file field other than file", (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('test', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', '[invalid-field]');
					expect(res.body).to.have.property('errorType', 'invalid-field');
				})
				.end(done);
		});
		it("don't upload a file to room with empty file", (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('file', '')
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', res.body.error);
				})
				.end(done);
		});
		it("don't upload a file to room with more than 1 file", (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('file', imgURL)
				.attach('file', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Just 1 file is allowed');
				})
				.end(done);
		});

		let fileUrl;
		it('upload a file to room', (done) => {
			request
				.post(api(`rooms.upload/${testChannel._id}`))
				.set(credentials)
				.attach('file', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					const { message } = res.body;
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.nested.property('message._id', message._id);
					expect(res.body).to.have.nested.property('message.rid', testChannel._id);
					expect(res.body).to.have.nested.property('message.file._id', message.file._id);
					expect(res.body).to.have.nested.property('message.file.type', message.file.type);
					fileUrl = `/file-upload/${message.file._id}/${message.file.name}`;
				})
				.end(done);
		});

		it('should be able to get the file', async () => {
			await request.get(fileUrl).set(credentials).expect('Content-Type', 'image/png').expect(200);
		});

		it('should be able to get the file when no access to the room', async () => {
			await request.get(fileUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200);
		});

		it('should not be able to get the file when no access to the room if setting blocks', async () => {
			await updateSetting('FileUpload_Restrict_to_room_members', true);
			await request.get(fileUrl).set(userCredentials).expect(403);
		});

		it('should be able to get the file if member and setting blocks outside access', async () => {
			await updateSetting('FileUpload_Restrict_to_room_members', true);
			await request.get(fileUrl).set(credentials).expect('Content-Type', 'image/png').expect(200);
		});

		it('should not be able to get the file without credentials', async () => {
			await request.get(fileUrl).attach('file', imgURL).expect(403);
		});

		it('should be able to get the file without credentials if setting allows', async () => {
			await updateSetting('FileUpload_ProtectFiles', false);
			await request.get(fileUrl).expect('Content-Type', 'image/png').expect(200);
		});
	});

	describe('/rooms.favorite', () => {
		let testChannel;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('should favorite the room when send favorite: true by roomName', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomName: testChannelName,
					favorite: true,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should unfavorite the room when send favorite: false by roomName', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomName: testChannelName,
					favorite: false,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should favorite the room when send favorite: true by roomId', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
					favorite: true,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});

		it('should unfavorite room when send favorite: false by roomId', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
					favorite: false,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});

		it('should return an error when send an invalid room', (done) => {
			request
				.post(api('rooms.favorite'))
				.set(credentials)
				.send({
					roomId: 'foo',
					favorite: false,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error');
				})
				.end(done);
		});
	});

	describe('/rooms.nameExists', () => {
		it('should return 401 unauthorized when user is not logged in', (done) => {
			request
				.get(api('rooms.nameExists'))
				.expect('Content-Type', 'application/json')
				.expect(401)
				.expect((res) => {
					expect(res.body).to.have.property('message');
				})
				.end(done);
		});

		// eslint-disable-next-line no-unused-vars
		let testChannel;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('should return true if this room name exists', (done) => {
			request
				.get(api('rooms.nameExists'))
				.set(credentials)
				.query({
					roomName: testChannelName,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('exists', true);
				})
				.end(done);
		});

		it('should return an error when send an invalid room', (done) => {
			request
				.get(api('rooms.nameExists'))
				.set(credentials)
				.query({
					roomId: 'foo',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error');
				})
				.end(done);
		});
	});

	describe('[/rooms.cleanHistory]', () => {
		let publicChannel;
		let privateChannel;
		let directMessageChannel;
		let user;
		beforeEach((done) => {
			const username = `user.test.${Date.now()}`;
			const email = `${username}@rocket.chat`;
			request
				.post(api('users.create'))
				.set(credentials)
				.send({ email, name: username, username, password })
				.end((err, res) => {
					user = res.body.user;
					done(err);
				});
		});

		let userCredentials;
		beforeEach((done) => {
			request
				.post(api('login'))
				.send({
					user: user.username,
					password,
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					userCredentials = {};
					userCredentials['X-Auth-Token'] = res.body.data.authToken;
					userCredentials['X-User-Id'] = res.body.data.userId;
				})
				.end(done);
		});
		afterEach((done) => {
			request
				.post(api('users.delete'))
				.set(credentials)
				.send({
					userId: user._id,
				})
				.end(done);
			user = undefined;
		});
		it('create a public channel', (done) => {
			createRoom({ type: 'c', name: `testeChannel${+new Date()}` }).end((err, res) => {
				publicChannel = res.body.channel;
				done();
			});
		});
		it('create a private channel', (done) => {
			createRoom({ type: 'p', name: `testPrivateChannel${+new Date()}` }).end((err, res) => {
				privateChannel = res.body.group;
				done(err);
			});
		});
		it('create a direct message', (done) => {
			createRoom({ type: 'd', username: 'rocket.cat' }).end((err, res) => {
				directMessageChannel = res.body.room.rid;
				done(err);
			});
		});
		it('should return success when send a valid public channel', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: publicChannel._id,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should successfully delete an image and thumbnail from public channel', (done) => {
			request
				.post(api(`rooms.upload/${publicChannel._id}`))
				.set(credentials)
				.attach('file', imgURL)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					const { message } = res.body;
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.nested.property('message._id', message._id);
					expect(res.body).to.have.nested.property('message.rid', publicChannel._id);
					expect(res.body).to.have.nested.property('message.file._id', message.file._id);
					expect(res.body).to.have.nested.property('message.file.type', message.file.type);
				});

			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: publicChannel._id,
					latest: '9999-12-31T23:59:59.000Z',
					oldest: '0001-01-01T00:00:00.000Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				});

			request
				.get(api('channels.files'))
				.set(credentials)
				.query({
					roomId: publicChannel._id,
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('files').and.to.be.an('array');
					expect(res.body.files).to.have.lengthOf(0);
				})
				.end(done);
		});
		it('should return success when send a valid private channel', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: privateChannel._id,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should return success when send a valid Direct Message channel', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(credentials)
				.send({
					roomId: directMessageChannel,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should return not allowed error when try deleting messages with user without permission', (done) => {
			request
				.post(api('rooms.cleanHistory'))
				.set(userCredentials)
				.send({
					roomId: directMessageChannel,
					latest: '2016-12-09T13:42:25.304Z',
					oldest: '2016-08-30T13:42:25.304Z',
				})
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-not-allowed');
				})
				.end(done);
		});
	});

	describe('[/rooms.info]', () => {
		let testChannel;
		let testGroup;
		let testDM;
		const expectedKeys = [
			'_id',
			'name',
			'fname',
			't',
			'msgs',
			'usersCount',
			'u',
			'customFields',
			'ts',
			'ro',
			'sysMes',
			'default',
			'_updatedAt',
		];
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		const testGroupName = `group.test.${Date.now()}-${Math.random()}`;
		after((done) => {
			closeRoom({ type: 'd', roomId: testDM._id }).then(done);
		});
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('create a group', (done) => {
			createRoom({ type: 'p', name: testGroupName }).end((err, res) => {
				testGroup = res.body.group;
				done();
			});
		});
		it('create a Direct message room with rocket.cat', (done) => {
			createRoom({ type: 'd', username: 'rocket.cat' }).end((err, res) => {
				testDM = res.body.room;
				done();
			});
		});
		it('should return the info about the created channel correctly searching by roomId', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testChannel._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created channel correctly searching by roomName', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomName: testChannel.name,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.all.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created group correctly searching by roomId', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testGroup._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.all.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created group correctly searching by roomName', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomName: testGroup.name,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.all.keys(expectedKeys);
				})
				.end(done);
		});
		it('should return the info about the created DM correctly searching by roomId', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testDM._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
				})
				.end(done);
		});
		it('should return name and _id of public channel when it has the "fields" query parameter limiting by name', (done) => {
			request
				.get(api('rooms.info'))
				.set(credentials)
				.query({
					roomId: testChannel._id,
					fields: JSON.stringify({ name: 1 }),
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('room').and.to.be.an('object');
					expect(res.body.room).to.have.property('name').and.to.be.equal(testChannelName);
					expect(res.body.room).to.have.all.keys(['_id', 'name']);
				})
				.end(done);
		});
	});

	describe('[/rooms.leave]', () => {
		let testChannel;
		let testGroup;
		let testDM;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		const testGroupName = `group.test.${Date.now()}-${Math.random()}`;
		after((done) => {
			closeRoom({ type: 'd', roomId: testDM._id }).then(done);
		});
		it('create an channel', (done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				done();
			});
		});
		it('create a group', (done) => {
			createRoom({ type: 'p', name: testGroupName }).end((err, res) => {
				testGroup = res.body.group;
				done();
			});
		});
		it('create a Direct message room with rocket.cat', (done) => {
			createRoom({ type: 'd', username: 'rocket.cat' }).end((err, res) => {
				testDM = res.body.room;
				done();
			});
		});
		it('should return an Error when trying leave a DM room', (done) => {
			request
				.post(api('rooms.leave'))
				.set(credentials)
				.send({
					roomId: testDM._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-not-allowed');
				})
				.end(done);
		});
		it('should return an Error when trying to leave a public channel and you are the last owner', (done) => {
			request
				.post(api('rooms.leave'))
				.set(credentials)
				.send({
					roomId: testChannel._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-you-are-last-owner');
				})
				.end(done);
		});
		it('should return an Error when trying to leave a private group and you are the last owner', (done) => {
			request
				.post(api('rooms.leave'))
				.set(credentials)
				.send({
					roomId: testGroup._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-you-are-last-owner');
				})
				.end(done);
		});
		it('should return an Error when trying to leave a public channel and not have the necessary permission(leave-c)', (done) => {
			updatePermission('leave-c', []).then(() => {
				request
					.post(api('rooms.leave'))
					.set(credentials)
					.send({
						roomId: testChannel._id,
					})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('errorType', 'error-not-allowed');
					})
					.end(done);
			});
		});
		it('should return an Error when trying to leave a private group and not have the necessary permission(leave-p)', (done) => {
			updatePermission('leave-p', []).then(() => {
				request
					.post(api('rooms.leave'))
					.set(credentials)
					.send({
						roomId: testGroup._id,
					})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('errorType', 'error-not-allowed');
					})
					.end(done);
			});
		});
		it('should leave the public channel when the room has at least another owner and the user has the necessary permission(leave-c)', (done) => {
			updatePermission('leave-c', ['admin']).then(() => {
				request
					.post(api('channels.addAll'))
					.set(credentials)
					.send({
						roomId: testChannel._id,
					})
					.end(() => {
						request
							.post(api('channels.addOwner'))
							.set(credentials)
							.send({
								roomId: testChannel._id,
								userId: 'rocket.cat',
							})
							.end(() => {
								request
									.post(api('rooms.leave'))
									.set(credentials)
									.send({
										roomId: testChannel._id,
									})
									.expect(200)
									.expect((res) => {
										expect(res.body).to.have.property('success', true);
									})
									.end(done);
							});
					});
			});
		});
		it('should leave the private group when the room has at least another owner and the user has the necessary permission(leave-p)', (done) => {
			updatePermission('leave-p', ['admin']).then(() => {
				request
					.post(api('groups.addAll'))
					.set(credentials)
					.send({
						roomId: testGroup._id,
					})
					.end(() => {
						request
							.post(api('groups.addOwner'))
							.set(credentials)
							.send({
								roomId: testGroup._id,
								userId: 'rocket.cat',
							})
							.end(() => {
								request
									.post(api('rooms.leave'))
									.set(credentials)
									.send({
										roomId: testGroup._id,
									})
									.expect(200)
									.expect((res) => {
										expect(res.body).to.have.property('success', true);
									})
									.end(done);
							});
					});
			});
		});
	});

	describe('/rooms.createDiscussion', () => {
		let testChannel;
		const testChannelName = `channel.test.${Date.now()}-${Math.random()}`;
		let messageSent;
		before((done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				sendSimpleMessage({
					roomId: testChannel._id,
				}).end((err, res) => {
					messageSent = res.body.message;
					done();
				});
			});
		});
		it('should throw an error when the user tries to create a discussion and the feature is disabled', (done) => {
			updateSetting('Discussion_enabled', false).then(() => {
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testChannel._id,
						t_name: 'valid name',
					})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
					})
					.end(() => updateSetting('Discussion_enabled', true).then(done));
			});
		});
		it('should throw an error when the user tries to create a discussion and does not have at least one of the required permissions', (done) => {
			updatePermission('start-discussion', []).then(() => {
				updatePermission('start-discussion-other-user', []).then(() => {
					request
						.post(api('rooms.createDiscussion'))
						.set(credentials)
						.send({
							prid: testChannel._id,
							t_name: 'valid name',
						})
						.expect(400)
						.expect((res) => {
							expect(res.body).to.have.property('success', false);
							expect(res.body).to.have.property('errorType', 'error-action-not-allowed');
						})
						.end(() => {
							updatePermission('start-discussion', ['admin', 'user', 'guest'])
								.then(() => updatePermission('start-discussion-other-user', ['admin', 'user', 'guest']))
								.then(done);
						});
				});
			});
		});
		it('should throw an error when the user tries to create a discussion without the required parameter "prid"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Body parameter "prid" is required.');
				})
				.end(done);
		});
		it('should throw an error when the user tries to create a discussion without the required parameter "t_name"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Body parameter "t_name" is required.');
				})
				.end(done);
		});
		it('should throw an error when the user tries to create a discussion with the required parameter invalid "users"(different from an array)', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: 'valid name',
					users: 'invalid-type-of-users',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'Body parameter "users" must be an array.');
				})
				.end(done);
		});
		it("should throw an error when the user tries to create a discussion with the channel's id invalid", (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: 'invalid-id',
					t_name: 'valid name',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-invalid-room');
				})
				.end(done);
		});
		it("should throw an error when the user tries to create a discussion with the message's id invalid", (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: 'valid name',
					pmid: 'invalid-message',
				})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('errorType', 'error-invalid-message');
				})
				.end(done);
		});
		it('should create a discussion successfully when send only the required parameters', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});
		it('should create a discussion successfully when send the required parameters plus the optional parameter "reply"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
					reply: 'reply from discussion tests',
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});
		it('should create a discussion successfully when send the required parameters plus the optional parameter "users"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
					reply: 'reply from discussion tests',
					users: ['rocket.cat'],
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});
		it('should create a discussion successfully when send the required parameters plus the optional parameter "pmid"', (done) => {
			request
				.post(api('rooms.createDiscussion'))
				.set(credentials)
				.send({
					prid: testChannel._id,
					t_name: `discussion-create-from-tests-${testChannel.name}`,
					reply: 'reply from discussion tests',
					users: ['rocket.cat'],
					pmid: messageSent._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussion').and.to.be.an('object');
					expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
					expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}`);
				})
				.end(done);
		});

		describe('it should create a *private* discussion if the parent channel is public and inside a private team', async () => {
			let privateTeam;

			it('should create a team', (done) => {
				request
					.post(api('teams.create'))
					.set(credentials)
					.send({
						name: `test-team-${Date.now()}`,
						type: 1,
					})
					.expect('Content-Type', 'application/json')
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('team');
						expect(res.body).to.have.nested.property('team._id');
						privateTeam = res.body.team;
					})
					.end(done);
			});

			it('should add the public channel to the team', (done) => {
				request
					.post(api('teams.addRooms'))
					.set(credentials)
					.send({
						rooms: [testChannel._id],
						teamId: privateTeam._id,
					})
					.expect('Content-Type', 'application/json')
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success');
					})
					.end(done);
			});

			it('should create a private discussion inside the public channel', (done) => {
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testChannel._id,
						t_name: `discussion-create-from-tests-${testChannel.name}-team`,
					})
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('discussion').and.to.be.an('object');
						expect(res.body.discussion).to.have.property('prid').and.to.be.equal(testChannel._id);
						expect(res.body.discussion).to.have.property('fname').and.to.be.equal(`discussion-create-from-tests-${testChannel.name}-team`);
						expect(res.body.discussion).to.have.property('t').and.to.be.equal('p');
					})
					.end(done);
			});
		});
	});

	describe('/rooms.getDiscussions', () => {
		let testChannel;
		const testChannelName = `channel.test.getDiscussions${Date.now()}-${Math.random()}`;
		let discussion;
		before((done) => {
			createRoom({ type: 'c', name: testChannelName }).end((err, res) => {
				testChannel = res.body.channel;
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testChannel._id,
						t_name: `discussion-create-from-tests-${testChannel.name}`,
					})
					.end((err, res) => {
						discussion = res.body.discussion;
						done();
					});
			});
		});
		after(() => closeRoom({ type: 'p', roomId: discussion._id }));
		it('should throw an error when the user tries to gets a list of discussion without a required parameter "roomId"', (done) => {
			request
				.get(api('rooms.getDiscussions'))
				.set(credentials)
				.query({})
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', 'The parameter "roomId" or "roomName" is required [error-roomid-param-not-provided]');
				})
				.end(done);
		});
		it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => {
			updatePermission('view-c-room', []).then(() => {
				request
					.get(api('rooms.getDiscussions'))
					.set(credentials)
					.query({})
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body).to.have.property('error', 'Not Allowed');
					})
					.end(() => updatePermission('view-c-room', ['admin', 'user', 'bot', 'anonymous']).then(done));
			});
		});
		it('should return a list of discussions with ONE discussion', (done) => {
			request
				.get(api('rooms.getDiscussions'))
				.set(credentials)
				.query({
					roomId: testChannel._id,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('discussions').and.to.be.an('array');
					expect(res.body.discussions).to.have.lengthOf(1);
				})
				.end(done);
		});
	});

	describe('[/rooms.autocomplete.channelAndPrivate]', () => {
		it('should return an error when the required parameter "selector" is not provided', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate'))
				.set(credentials)
				.query({})
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body.error).to.be.equal("The 'selector' param is required");
				})
				.end(done);
		});
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate?selector={}'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
				})
				.end(done);
		});
	});
	describe('[/rooms.autocomplete.channelAndPrivate.withPagination]', () => {
		it('should return an error when the required parameter "selector" is not provided', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate.withPagination'))
				.set(credentials)
				.query({})
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body.error).to.be.equal("The 'selector' param is required");
				})
				.end(done);
		});
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate.withPagination?selector={}'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
					expect(res.body).to.have.property('total');
				})
				.end(done);
		});
		it('should return the rooms to fill auto complete even requested with count and offset params', (done) => {
			request
				.get(api('rooms.autocomplete.channelAndPrivate.withPagination?selector={}'))
				.set(credentials)
				.query({
					count: 5,
					offset: 0,
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
					expect(res.body).to.have.property('total');
				})
				.end(done);
		});
	});

	describe('[/rooms.autocomplete.availableForTeams]', () => {
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.availableForTeams'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
				})
				.end(done);
		});
		it('should return the filtered rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.availableForTeams?name=group'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
				})
				.end(done);
		});
	});
	describe('[/rooms.autocomplete.adminRooms]', () => {
		let testGroup;
		const testGroupName = `channel.test.adminRoom${Date.now()}-${Math.random()}`;
		const name = {
			name: testGroupName,
		};
		before((done) => {
			createRoom({ type: 'p', name: testGroupName }).end((err, res) => {
				testGroup = res.body.group;
				request
					.post(api('rooms.createDiscussion'))
					.set(credentials)
					.send({
						prid: testGroup._id,
						t_name: `${testGroupName}-discussion`,
					})
					.end(done);
			});
		});
		(IS_EE ? it : it.skip)('should return an error when the required parameter "selector" is not provided', (done) => {
			updateEEPermission('can-audit', ['admin']).then(() => {
				request
					.get(api('rooms.autocomplete.adminRooms'))
					.set(credentials)
					.query({})
					.expect('Content-Type', 'application/json')
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body.error).to.be.equal("The 'selector' param is required");
					})
					.end(done);
			});
		});
		it('should return the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.adminRooms?selector={}'))
				.set(credentials)
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
				})
				.end(done);
		});
		it('should return FIX	 the rooms to fill auto complete', (done) => {
			request
				.get(api('rooms.autocomplete.adminRooms?'))
				.set(credentials)
				.query({
					selector: JSON.stringify(name),
				})
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('items').and.to.be.an('array');
					expect(res.body).to.have.property('items').that.have.lengthOf(2);
				})
				.end(done);
		});
	});
	describe('/rooms.adminRooms', () => {
		const suffix = `test-${Date.now()}`;
		const fnameRoom = `Ελληνικά-${suffix}`;
		const nameRoom = `Ellinika-${suffix}`;

		before((done) => {
			updateSetting('UI_Allow_room_names_with_special_chars', true).then(() => {
				createRoom({ type: 'p', name: fnameRoom }).end(done);
			});
		});

		it('should throw an error when the user tries to gets a list of discussion and he cannot access the room', (done) => {
			updatePermission('view-room-administration', []).then(() => {
				request
					.get(api('rooms.adminRooms'))
					.set(credentials)
					.expect(400)
					.expect((res) => {
						expect(res.body).to.have.property('success', false);
						expect(res.body.error).to.be.equal('error-not-authorized');
					})
					.end(() => updatePermission('view-room-administration', ['admin']).then(done));
			});
		});
		it('should return a list of admin rooms', (done) => {
			request
				.get(api('rooms.adminRooms'))
				.set(credentials)
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('rooms').and.to.be.an('array');
					expect(res.body).to.have.property('offset');
					expect(res.body).to.have.property('total');
					expect(res.body).to.have.property('count');
				})
				.end(done);
		});
		it('should return a list of admin rooms even requested with count and offset params', (done) => {
			request
				.get(api('rooms.adminRooms'))
				.set(credentials)
				.query({
					count: 5,
					offset: 0,
				})
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
					expect(res.body).to.have.property('rooms').and.to.be.an('array');
					expect(res.body).to.have.property('offset');
					expect(res.body).to.have.property('total');
					expect(res.body).to.have.property('count');
				})
				.end(done);
		});
		it('should search the list of admin rooms using non-latin characters when UI_Allow_room_names_with_special_chars setting is toggled', (done) => {
			updateSetting('UI_Allow_room_names_with_special_chars', true).then(() => {
				request
					.get(api('rooms.adminRooms'))
					.set(credentials)
					.query({
						filter: fnameRoom,
					})
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('rooms').and.to.be.an('array');
						expect(res.body.rooms).to.have.lengthOf(1);
						expect(res.body.rooms[0].fname).to.be.equal(fnameRoom);
						expect(res.body).to.have.property('offset');
						expect(res.body).to.have.property('total');
						expect(res.body).to.have.property('count');
					})
					.end(done);
			});
		});
		it('should search the list of admin rooms using latin characters only when UI_Allow_room_names_with_special_chars setting is disabled', (done) => {
			updateSetting('UI_Allow_room_names_with_special_chars', false).then(() => {
				request
					.get(api('rooms.adminRooms'))
					.set(credentials)
					.query({
						filter: nameRoom,
					})
					.expect(200)
					.expect((res) => {
						expect(res.body).to.have.property('success', true);
						expect(res.body).to.have.property('rooms').and.to.be.an('array');
						expect(res.body.rooms).to.have.lengthOf(1);
						expect(res.body.rooms[0].name).to.be.equal(nameRoom);
						expect(res.body).to.have.property('offset');
						expect(res.body).to.have.property('total');
						expect(res.body).to.have.property('count');
					})
					.end(done);
			});
		});
	});

	describe('update group dms name', () => {
		let testUser;
		let roomId;

		before(async () => {
			testUser = await createUser();

			const rocketcat = 'rocket.cat';
			const usernames = [testUser.username, rocketcat].join(',');

			const result = await request.post(api('dm.create')).set(credentials).send({
				usernames,
			});

			roomId = result.body.room.rid;
		});

		it('should update group name if user changes username', (done) => {
			updateSetting('UI_Use_Real_Name', false).then(() => {
				request
					.post(api('users.update'))
					.set(credentials)
					.send({
						userId: testUser._id,
						data: {
							username: `changed.username.${testUser.username}`,
						},
					})
					.end(() => {
						request
							.get(api('subscriptions.getOne'))
							.set(credentials)
							.query({ roomId })
							.end((err, res) => {
								const { subscription } = res.body;
								expect(subscription.name).to.equal(`rocket.cat,changed.username.${testUser.username}`);
								done();
							});
					});
			});
		});

		it('should update group name if user changes name', (done) => {
			updateSetting('UI_Use_Real_Name', true).then(() => {
				request
					.post(api('users.update'))
					.set(credentials)
					.send({
						userId: testUser._id,
						data: {
							name: `changed.name.${testUser.username}`,
						},
					})
					.end(() => {
						request
							.get(api('subscriptions.getOne'))
							.set(credentials)
							.query({ roomId })
							.end((err, res) => {
								const { subscription } = res.body;
								expect(subscription.fname).to.equal(`changed.name.${testUser.username}, Rocket.Cat`);
								done();
							});
					});
			});
		});
	});

	describe('/rooms.delete', () => {
		let testChannel;
		before('create an channel', async () => {
			const result = await createRoom({ type: 'c', name: `channel.test.${Date.now()}-${Math.random()}` });
			testChannel = result.body.channel;
		});
		it('should throw an error when roomId is not provided', (done) => {
			request
				.post(api('rooms.delete'))
				.set(credentials)
				.send({})
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
					expect(res.body).to.have.property('error', "The 'roomId' param is required");
				})
				.end(done);
		});
		it('should delete a room when the request is correct', (done) => {
			request
				.post(api('rooms.delete'))
				.set(credentials)
				.send({ roomId: testChannel._id })
				.expect('Content-Type', 'application/json')
				.expect(200)
				.expect((res) => {
					expect(res.body).to.have.property('success', true);
				})
				.end(done);
		});
		it('should throw an error when the room id doesn exist', (done) => {
			request
				.post(api('rooms.delete'))
				.set(credentials)
				.send({ roomId: 'invalid' })
				.expect('Content-Type', 'application/json')
				.expect(400)
				.expect((res) => {
					expect(res.body).to.have.property('success', false);
				})
				.end(done);
		});
	});
});


import { Field, Flex, InputBox, Skeleton } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import React from 'react';

const SettingSkeleton = (): ReactElement => (
	<Field>
		<Flex.Item align='stretch'>
			<Field.Label>
				<Skeleton width='25%' />
			</Field.Label>
		</Flex.Item>
		<Field.Row>
			<InputBox.Skeleton />
		</Field.Row>
	</Field>
);

export default SettingSkeleton;


import { addMigration } from '../../lib/migrations';
import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions';

addMigration({
	version: 279,
	up() {
		upsertPermissions();
	},
});


import { Box } from '@rocket.chat/fuselage';
import type { ComponentProps } from 'react';
import React, { forwardRef } from 'react';

const UserCardContainer = forwardRef(function UserCardContainer(props: ComponentProps<typeof Box>, ref) {
	return <Box ref={ref} rcx-user-card bg='surface' elevation='2' p='x24' display='flex' borderRadius='x4' width='439px' {...props} />;
});

export default UserCardContainer;


keep keep keep keep replace keep keep keep


import type { IMessage } from '@rocket.chat/core-typings';
import { isEditedMessage } from '@rocket.chat/core-typings';
import { useUser } from '@rocket.chat/ui-contexts';
import type { ScrollValues } from 'rc-scrollbars';
import { useCallback, useEffect, useRef } from 'react';

import { callbacks } from '../../../../../../lib/callbacks';
import { useRoom } from '../../../contexts/RoomContext';

export const useLegacyThreadMessageListScrolling = (mainMessage: IMessage) => {
	const listWrapperRef = useRef<HTMLDivElement>(null);
	const listRef = useRef<HTMLElement>(null);

	const atBottomRef = useRef(true);

	const onScroll = useCallback(({ scrollHeight, scrollTop, clientHeight }: ScrollValues) => {
		atBottomRef.current = scrollTop >= scrollHeight - clientHeight;
	}, []);

	const sendToBottomIfNecessary = useCallback(() => {
		if (atBottomRef.current === true) {
			const listWrapper = listWrapperRef.current;

			listWrapper?.scrollTo(30, listWrapper.scrollHeight);
		}
	}, []);

	const room = useRoom();
	const user = useUser();

	useEffect(() => {
		callbacks.add(
			'streamNewMessage',
			(msg: IMessage) => {
				if (room._id !== msg.rid || isEditedMessage(msg) || msg.tmid !== mainMessage._id) {
					return;
				}

				if (msg.u._id === user?._id) {
					atBottomRef.current = true;
					sendToBottomIfNecessary();
				}
			},
			callbacks.priority.MEDIUM,
			`thread-scroll-${room._id}`,
		);

		return () => {
			callbacks.remove('streamNewMessage', `thread-scroll-${room._id}`);
		};
	}, [room._id, sendToBottomIfNecessary, user?._id, mainMessage._id]);

	useEffect(() => {
		const observer = new ResizeObserver(() => {
			sendToBottomIfNecessary();
		});

		if (listWrapperRef.current) observer.observe(listWrapperRef.current);
		if (listRef.current) observer.observe(listRef.current);

		return () => {
			observer.disconnect();
		};
	}, [sendToBottomIfNecessary]);

	return { listWrapperRef, listRef, requestScrollToBottom: sendToBottomIfNecessary, onScroll };
};


import { Box } from '@rocket.chat/fuselage';
import { useLayout } from '@rocket.chat/ui-contexts';
import type { ReactNode, ReactElement } from 'react';
import React, { useRef } from 'react';

import DesktopToolboxDropdown from './DesktopToolboxDropdown';
import MobileToolboxDropdown from './MobileToolboxDropdown';

type ToolboxDropdownProps<R> = {
	children: ReactNode;
	reference: React.RefObject<R>;
};

const ToolboxDropdown = <TReferenceElement extends HTMLElement>({
	children,
	reference,
}: ToolboxDropdownProps<TReferenceElement>): ReactElement => {
	const { isMobile } = useLayout();
	const target = useRef<HTMLButtonElement>(null);

	const Dropdown = isMobile ? MobileToolboxDropdown : DesktopToolboxDropdown;

	return (
		<>
			<Box position='fixed' inset={0} />
			<Dropdown ref={target} reference={reference}>
				{children}
			</Dropdown>
		</>
	);
};

export default ToolboxDropdown;


// Styles
import '../../stylus/components/_selection-controls.styl'

// Components
import VIcon from '../VIcon'
// import { VFadeTransition } from '../transitions'

// Mixins
import Selectable from '../../mixins/selectable'

/* @vue/component */
export default {
  name: 'v-checkbox',

  mixins: [
    Selectable
  ],

  props: {
    indeterminate: Boolean,
    indeterminateIcon: {
      type: String,
      default: '$vuetify.icons.checkboxIndeterminate'
    },
    onIcon: {
      type: String,
      default: '$vuetify.icons.checkboxOn'
    },
    offIcon: {
      type: String,
      default: '$vuetify.icons.checkboxOff'
    }
  },

  data: vm => ({
    inputIndeterminate: vm.indeterminate
  }),

  computed: {
    classes () {
      return {
        'v-input--selection-controls': true,
        'v-input--checkbox': true
      }
    },
    computedIcon () {
      if (this.inputIndeterminate) {
        return this.indeterminateIcon
      } else if (this.isActive) {
        return this.onIcon
      } else {
        return this.offIcon
      }
    }
  },

  watch: {
    indeterminate (val) {
      this.inputIndeterminate = val
    }
  },

  methods: {
    genCheckbox () {
      return this.$createElement('div', {
        staticClass: 'v-input--selection-controls__input'
      }, [
        this.genInput('checkbox', {
          'aria-checked': this.inputIndeterminate
            ? 'mixed'
            : this.isActive.toString()
        }),
        !this.disabled && this.genRipple({
          'class': this.classesSelectable
        }),
        this.$createElement(VIcon, {
          'class': this.classesSelectable,
          props: {
            dark: this.dark,
            light: this.light
          }
        }, this.computedIcon)
      ])
    },
    genDefaultSlot () {
      return [
        this.genCheckbox(),
        this.genLabel()
      ]
    }
  }
}


import '../../stylus/components/_small-dialog.styl'

// Mixins
import Returnable from '../../mixins/returnable'

// Utils
import { keyCodes } from '../../util/helpers'

import VBtn from '../VBtn'
import VMenu from '../VMenu'

/* @vue/component */
export default {
  name: 'v-edit-dialog',

  mixins: [ Returnable ],

  props: {
    cancelText: {
      default: 'Cancel'
    },
    large: Boolean,
    lazy: Boolean,
    persistent: Boolean,
    saveText: {
      default: 'Save'
    },
    transition: {
      type: String,
      default: 'slide-x-reverse-transition'
    }
  },

  data () {
    return {
      isActive: false
    }
  },

  watch: {
    isActive (val) {
      if (val) {
        this.$emit('open')
        setTimeout(this.focus, 50) // Give DOM time to paint
      } else {
        this.$emit('close')
      }
    }
  },

  methods: {
    cancel () {
      this.isActive = false
      this.$emit('cancel')
    },
    focus () {
      const input = this.$refs.content.querySelector('input')
      input && input.focus()
    },
    genButton (fn, text) {
      return this.$createElement(VBtn, {
        props: {
          flat: true,
          color: 'primary',
          light: true
        },
        on: { click: fn }
      }, text)
    },
    genActions () {
      return this.$createElement('div', {
        'class': 'v-small-dialog__actions'
      }, [
        this.genButton(this.cancel, this.cancelText),
        this.genButton(() => {
          this.save(this.returnValue)
          this.$emit('save')
        }, this.saveText)
      ])
    },
    genContent () {
      return this.$createElement('div', {
        on: {
          keydown: e => {
            const input = this.$refs.content.querySelector('input')
            e.keyCode === keyCodes.esc && this.cancel()
            if (e.keyCode === keyCodes.enter && input) {
              this.save(input.value)
              this.$emit('save')
            }
          }
        },
        ref: 'content'
      }, [this.$slots.input])
    }
  },

  render (h) {
    return h(VMenu, {
      'class': 'v-small-dialog',
      props: {
        contentClass: 'v-small-dialog__content',
        transition: this.transition,
        origin: 'top right',
        right: true,
        value: this.isActive,
        closeOnClick: !this.persistent,
        closeOnContentClick: false,
        lazy: this.lazy
      },
      on: {
        input: val => (this.isActive = val)
      }
    }, [
      h('a', {
        slot: 'activator'
      }, this.$slots.default),
      this.genContent(),
      this.large ? this.genActions() : null
    ])
  }
}


import { test } from '@/test'
import {
  VBreadcrumbs,
  VBreadcrumbsItem
} from '@/components/VBreadcrumbs'
import Vue from 'vue'

test('VBreadcrumbs.js', ({ mount, compileToFunctions }) => {
  it('should have breadcrumbs classes', () => {
    const wrapper = mount(VBreadcrumbs)

    expect(wrapper.hasClass('v-breadcrumbs')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should inject slot to children', () => {
    const { render } = compileToFunctions(`
      <v-breadcrumbs>
        <v-breadcrumbs-item v-for="i in 4" :key="i"/>
      </v-breadcrumbs>
    `)
    const component = Vue.component('test', {
      components: {
        VBreadcrumbs, VBreadcrumbsItem
      },
      render
    })
    const wrapper = mount(component)

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should use a custom divider slot', () => {
    const { render } = compileToFunctions(`
      <v-breadcrumbs>
        <template slot="divider">/divider/</template>
        <v-breadcrumbs-item/>
        <v-breadcrumbs-item/>
      </v-breadcrumbs>
    `)
    const component = Vue.component('test', {
      components: {
        VBreadcrumbs, VBreadcrumbsItem
      },
      render
    })
    const wrapper = mount(component)

    expect(wrapper.html()).toMatchSnapshot()
  })

  // TODO: Inline styles not working in jest?
  it('should use custom justify props', () => {
    const wrapper = mount(VBreadcrumbs)

    wrapper.setProps({ justifyCenter: true, justifyEnd: false })
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({ justifyCenter: false, justifyEnd: true })
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should not create dividers for non-items', () => {
    const { render } = compileToFunctions(`
      <v-breadcrumbs>

        <v-breadcrumbs-item/>

        <v-breadcrumbs-item/>
      </v-breadcrumbs>
    `)
    const component = Vue.component('test', {
      components: {
        VBreadcrumbs, VBreadcrumbsItem
      },
      render
    })
    const wrapper = mount(component)
    expect(wrapper.html()).toMatchSnapshot()
  })

  // TODO: this always passes in jest, needs to be e2e
  it.skip('should remove dividers when items change', async () => {
    const component = Vue.component('test', {
      data: () => ({ items: 3 }),
      render (h) {
        return h(VBreadcrumbs, Array.from(Array(this.items), (_, i) => (
          h(VBreadcrumbsItem, { key: i }, [i])
        )))
      }
    })

    const wrapper = mount(component)
    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setData({ items: 1 })
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })
})


import Vue from 'vue'
import { test } from '@/test'
import VExpansionPanel from '@/components/VExpansionPanel'
import VExpansionPanelContent from '@/components/VExpansionPanel/VExpansionPanelContent'

const createPanel = props => {
  return Vue.component('test', {
    render: h => {
      const panelContent = h(VExpansionPanelContent, [
        h('div', { slot: 'header' }, 'header'),
        'content'
      ])
      return h(VExpansionPanel, { props }, [panelContent])
    }
  })
}

test('VExpansionPanel.js', ({ mount, compileToFunctions }) => {
  it('should render component and match snapshot', async () => {
    const wrapper = mount(createPanel())

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.find('.v-expansion-panel__header')[0].trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render inset component', () => {
    const wrapper = mount(createPanel({
      inset: true
    }))

    expect(wrapper.hasClass('v-expansion-panel--inset')).toBe(true)
  })

  it('should render popout component', () => {
    const wrapper = mount(createPanel({
      popout: true
    }))

    expect(wrapper.hasClass('v-expansion-panel--popout')).toBe(true)
  })

  it('should render an expanded component and match snapshot', async () => {
    const wrapper = mount(createPanel({
      expand: true
    }))

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.find('.v-expansion-panel__header')[0].trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should show content on v-model change', async () => {
    const wrapper = mount(VExpansionPanel, {
      slots: {
        default: VExpansionPanelContent.options
      }
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({ value: 0 })
    await wrapper.vm.$nextTick()

    expect(wrapper.first('.v-expansion-panel__container--active')).not.toBe(null)
  })

  it('should show content on mount using v-model', async () => {
    const wrapper = mount(VExpansionPanel, {
      propsData: {
        value: 0
      },
      slots: {
        default: VExpansionPanelContent.options
      }
    })

    await wrapper.vm.$nextTick()
    expect(wrapper.first('.v-expansion-panel__container--active')).not.toBe(null)
  })

  it('should allow array v-model when using expand prop', async () => {
    const wrapper = mount(VExpansionPanel, {
      propsData: {
        expand: true,
        value: [1, 3]
      },
      slots: {
        default: [VExpansionPanelContent, VExpansionPanelContent, VExpansionPanelContent, VExpansionPanelContent]
      }
    })

    await wrapper.vm.$nextTick()
    expect(wrapper.find('.v-expansion-panel__container--active').length).toBe(2)
  })

  it('should reset v-model when disabling expand', async () => {
    const fn = jest.fn()
    const wrapper = mount(VExpansionPanel, {
      propsData: {
        expand: true,
        value: [true, true]
      },
      slots: {
        default: [VExpansionPanelContent, VExpansionPanelContent]
      }
    })

    wrapper.instance().$on('input', fn)

    await wrapper.vm.$nextTick()
    expect(wrapper.find('.v-expansion-panel__container--active').length).toBe(2)

    wrapper.setProps({ expand: false })
    await wrapper.vm.$nextTick()
    expect(fn).toHaveBeenCalledWith(null)
  })

  it('should reset v-model when disabling expand', async () => {
    const input = jest.fn()
    const wrapper = mount(VExpansionPanel, {
      propsData: {
        expand: true,
        value: [false]
      },
      slots: {
        default: [VExpansionPanelContent]
      }
    })

    wrapper.instance().$on('input', input)

    await wrapper.vm.$nextTick()
    expect(wrapper.find('.v-expansion-panel__container--active').length).toBe(0)

    wrapper.setProps({ expand: false })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith(null)
  })

  it('should keep a single item open when disabling expand', async () => {
    const input = jest.fn()
    const wrapper = mount(VExpansionPanel, {
      propsData: {
        expand: true,
        value: [false, true]
      },
      slots: {
        default: [VExpansionPanelContent, VExpansionPanelContent]
      }
    })
    wrapper.vm.$on('input', input)

    wrapper.setProps({ expand: false })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith(1)
  })

  it('should keep the current item open when enabling expand', async () => {
    const input = jest.fn()
    const wrapper = mount(VExpansionPanel, {
      propsData: {
        expand: false,
        value: 1
      },
      slots: {
        default: [VExpansionPanelContent, VExpansionPanelContent]
      }
    })
    wrapper.vm.$on('input', input)

    wrapper.setProps({ expand: true })
    await wrapper.vm.$nextTick()
    expect(input).toHaveBeenCalledWith([false, true])
    expect(wrapper.find('.v-expansion-panel__container--active').length).toBe(1)
  })
})


/** @deprecated */
prependIconCb: Function,
readonly: Boolean,
tabindex: { default: 0 },
value: { required: false }
},
data: vm => ({
lazyValue: vm.value,

keep keep keep replace keep keep keep keep keep

// Styles
import '../../stylus/components/_sliders.styl'

// Components
import { VScaleTransition } from '../transitions'

// Extensions
import VInput from '../VInput'

// Directives
import ClickOutside from '../../directives/click-outside'

// Utilities
import {
  addOnceEventListener,
  convertToUnit,
  createRange,
  keyCodes,
  deepEqual
} from '../../util/helpers'
import { consoleWarn } from '../../util/console'

/* @vue/component */
export default {
  name: 'v-slider',

  directives: { ClickOutside },

  extends: VInput,

  props: {
    alwaysDirty: Boolean,
    inverseLabel: Boolean,
    label: String,
    min: {
      type: [Number, String],
      default: 0
    },
    max: {
      type: [Number, String],
      default: 100
    },
    range: Boolean,
    step: {
      type: [Number, String],
      default: 1
    },
    ticks: {
      type: [Boolean, String],
      default: false,
      validator: v => typeof v === 'boolean' || v === 'always'
    },
    tickLabels: {
      type: Array,
      default: () => ([])
    },
    tickSize: {
      type: [Number, String],
      default: 1
    },
    thumbColor: {
      type: String,
      default: null
    },
    thumbLabel: {
      type: [Boolean, String],
      default: null,
      validator: v => typeof v === 'boolean' || v === 'always'
    },
    thumbSize: {
      type: [Number, String],
      default: 32
    },
    trackColor: {
      type: String,
      default: null
    },
    value: [Number, String]
  },

  data: vm => ({
    app: {},
    defaultColor: 'primary',
    isActive: false,
    keyPressed: 0,
    lazyValue: typeof vm.value !== 'undefined' ? vm.value : Number(vm.min),
    oldValue: null
  }),

  computed: {
    classes () {
      return {
        'v-input--slider': true,
        'v-input--slider--ticks': this.showTicks,
        'v-input--slider--inverse-label': this.inverseLabel,
        'v-input--slider--ticks-labels': this.tickLabels.length > 0,
        'v-input--slider--thumb-label': this.thumbLabel ||
          this.$scopedSlots.thumbLabel
      }
    },
    showTicks () {
      return this.tickLabels.length > 0 ||
        (!this.disabled && this.stepNumeric && !!this.ticks)
    },
    showThumbLabel () {
      return !this.disabled && (
        !!this.thumbLabel ||
        this.thumbLabel === '' ||
        this.$scopedSlots['thumb-label']
      )
    },
    computedColor () {
      if (this.disabled) return null
      return this.validationState || this.color || this.defaultColor
    },
    computedTrackColor () {
      return this.disabled ? null : (this.trackColor || null)
    },
    computedThumbColor () {
      if (this.disabled || !this.isDirty) return null
      return this.validationState || this.thumbColor || this.color || this.defaultColor
    },
    internalValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        const { min, max } = this

        // Round value to ensure the
        // entire slider range can
        // be selected with step
        const value = this.roundValue(Math.min(Math.max(val, min), max))

        if (value === this.lazyValue) return

        this.lazyValue = value

        this.$emit('input', value)
        this.validate()
      }
    },
    stepNumeric () {
      return this.step > 0 ? parseFloat(this.step) : 0
    },
    trackFillStyles () {
      let left = this.$vuetify.rtl ? 'auto' : 0
      let right = this.$vuetify.rtl ? 0 : 'auto'
      let width = `${this.inputWidth}%`

      if (this.disabled) width = `calc(${this.inputWidth}% - 8px)`

      return {
        transition: this.trackTransition,
        left,
        right,
        width
      }
    },
    trackPadding () {
      return (
        this.isActive ||
        this.inputWidth > 0 ||
        this.disabled
      ) ? 0 : 7
    },
    trackStyles () {
      const trackPadding = this.disabled ? `calc(${this.inputWidth}% + 8px)` : `${this.trackPadding}px`
      let left = this.$vuetify.rtl ? 'auto' : trackPadding
      let right = this.$vuetify.rtl ? trackPadding : 'auto'
      let width = this.disabled
        ? `calc(${100 - this.inputWidth}% - 8px)`
        : '100%'

      return {
        transition: this.trackTransition,
        left,
        right,
        width
      }
    },
    tickStyles () {
      const size = Number(this.tickSize)

      return {
        'border-width': `${size}px`,
        'border-radius': size > 1 ? '50%' : null,
        transform: size > 1 ? `translateX(-${size}px) translateY(-${size - 1}px)` : null
      }
    },
    trackTransition () {
      return this.keyPressed >= 2 ? 'none' : ''
    },
    numTicks () {
      return Math.ceil((this.max - this.min) / this.stepNumeric)
    },
    inputWidth () {
      return (this.roundValue(this.internalValue) - this.min) / (this.max - this.min) * 100
    },
    isDirty () {
      return this.internalValue > this.min ||
        this.alwaysDirty
    }
  },

  watch: {
    min (val) {
      val > this.internalValue && this.$emit('input', parseFloat(val))
    },
    max (val) {
      val < this.internalValue && this.$emit('input', parseFloat(val))
    },
    value (val) {
      this.internalValue = val
    }
  },

  mounted () {
    // Without a v-app, iOS does not work with body selectors
    this.app = document.querySelector('[data-app]') ||
      consoleWarn('Missing v-app or a non-body wrapping element with the [data-app] attribute', this)
  },

  methods: {
    genDefaultSlot () {
      const children = [this.genLabel()]
      const slider = this.genSlider()

      this.inverseLabel
        ? children.unshift(slider)
        : children.push(slider)

      return children
    },
    genListeners () {
      return Object.assign({}, {
        blur: this.onBlur,
        click: this.onSliderClick,
        focus: this.onFocus,
        keydown: this.onKeyDown,
        keyup: this.onKeyUp
      })
    },
    genInput () {
      return this.$createElement('input', {
        attrs: {
          'aria-label': this.label,
          name: this.name,
          role: 'slider',
          tabindex: this.disabled ? -1 : undefined,
          type: 'range',
          value: this.internalValue,
          readonly: true,
          'aria-readonly': String(this.readonly)
        },
        on: this.genListeners(),
        ref: 'input'
      })
    },
    genSlider () {
      return this.$createElement('div', {
        staticClass: 'v-slider',
        'class': {
          'v-slider--is-active': this.isActive
        },
        directives: [{
          name: 'click-outside',
          value: this.onBlur
        }]
      }, this.genChildren())
    },
    genChildren () {
      return [
        this.genInput(),
        this.genTrackContainer(),
        this.genSteps(),
        this.genThumbContainer(
          this.internalValue,
          this.inputWidth,
          this.isFocused || this.isActive,
          this.onThumbMouseDown
        )
      ]
    },
    genSteps () {
      if (!this.step || !this.showTicks) return null

      const ticks = createRange(this.numTicks + 1).map(i => {
        const children = []

        if (this.tickLabels[i]) {
          children.push(this.$createElement('span', this.tickLabels[i]))
        }

        return this.$createElement('span', {
          key: i,
          staticClass: 'v-slider__ticks',
          class: {
            'v-slider__ticks--always-show': this.ticks === 'always' ||
              this.tickLabels.length > 0
          },
          style: {
            ...this.tickStyles,
            left: `${i * (100 / this.numTicks)}%`
          }
        }, children)
      })

      return this.$createElement('div', {
        staticClass: 'v-slider__ticks-container'
      }, ticks)
    },
    genThumb () {
      return this.$createElement('div', {
        staticClass: 'v-slider__thumb',
        'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor)
      })
    },
    genThumbContainer (value, valueWidth, isActive, onDrag) {
      const children = [this.genThumb()]

      const thumbLabelContent = this.getLabel(value)
      this.showThumbLabel && children.push(this.genThumbLabel(thumbLabelContent))

      return this.$createElement('div', {
        staticClass: 'v-slider__thumb-container',
        'class': this.addTextColorClassChecks({
          'v-slider__thumb-container--is-active': isActive,
          'v-slider__thumb-container--show-label': this.showThumbLabel
        }, this.computedThumbColor),
        style: {
          transition: this.trackTransition,
          left: `${this.$vuetify.rtl ? 100 - valueWidth : valueWidth}%`
        },
        on: {
          touchstart: onDrag,
          mousedown: onDrag
        }
      }, children)
    },
    genThumbLabel (content) {
      const size = convertToUnit(this.thumbSize)

      return this.$createElement(VScaleTransition, {
        props: { origin: 'bottom center' }
      }, [
        this.$createElement('div', {
          staticClass: 'v-slider__thumb-label__container',
          directives: [
            {
              name: 'show',
              value: this.isFocused || this.isActive || this.thumbLabel === 'always'
            }
          ]
        }, [
          this.$createElement('div', {
            staticClass: 'v-slider__thumb-label',
            'class': this.addBackgroundColorClassChecks({}, this.computedThumbColor),
            style: {
              height: size,
              width: size
            }
          }, [content])
        ])
      ])
    },
    genTrackContainer () {
      const children = [
        this.$createElement('div', {
          staticClass: 'v-slider__track',
          'class': this.addBackgroundColorClassChecks({}, this.computedTrackColor),
          style: this.trackStyles
        }),
        this.$createElement('div', {
          staticClass: 'v-slider__track-fill',
          'class': this.addBackgroundColorClassChecks(),
          style: this.trackFillStyles
        })
      ]

      return this.$createElement('div', {
        staticClass: 'v-slider__track__container',
        ref: 'track'
      }, children)
    },
    getLabel (value) {
      return this.$scopedSlots['thumb-label']
        ? this.$scopedSlots['thumb-label']({ value })
        : this.$createElement('span', value)
    },
    onBlur (e) {
      if (this.keyPressed === 2) return

      this.isActive = false
      this.isFocused = false
      this.$emit('blur', e)
    },
    onFocus (e) {
      this.isFocused = true
      this.$emit('focus', e)
    },
    onThumbMouseDown (e) {
      this.oldValue = this.internalValue
      this.keyPressed = 2
      const options = { passive: true }
      this.isActive = true
      this.isFocused = false

      if ('touches' in e) {
        this.app.addEventListener('touchmove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'touchend', this.onMouseUp)
      } else {
        this.app.addEventListener('mousemove', this.onMouseMove, options)
        addOnceEventListener(this.app, 'mouseup', this.onMouseUp)
      }

      this.$emit('start', this.internalValue)
    },
    onMouseUp () {
      this.keyPressed = 0
      const options = { passive: true }
      this.isActive = false
      this.isFocused = false
      this.app.removeEventListener('touchmove', this.onMouseMove, options)
      this.app.removeEventListener('mousemove', this.onMouseMove, options)

      this.$emit('end', this.internalValue)
      if (!deepEqual(this.oldValue, this.internalValue)) {
        this.$emit('change', this.internalValue)
      }
    },
    onMouseMove (e) {
      const { value, isInsideTrack } = this.parseMouseMove(e)

      if (isInsideTrack) {
        this.setInternalValue(value)
      }
    },
    onKeyDown (e) {
      if (this.disabled || this.readonly) return

      const value = this.parseKeyDown(e)

      if (value == null) return

      this.setInternalValue(value)
      this.$emit('change', value)
    },
    onKeyUp () {
      this.keyPressed = 0
    },
    onSliderClick (e) {
      this.isFocused = true
      this.onMouseMove(e)
      this.$emit('change', this.internalValue)
    },
    parseMouseMove (e) {
      const {
        left: offsetLeft,
        width: trackWidth
      } = this.$refs.track.getBoundingClientRect()
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
      // It is possible for left to be NaN, force to number
      let left = Math.min(Math.max((clientX - offsetLeft) / trackWidth, 0), 1) || 0

      if (this.$vuetify.rtl) left = 1 - left

      const isInsideTrack = clientX >= offsetLeft - 8 && clientX <= offsetLeft + trackWidth + 8
      const value = parseFloat(this.min) + left * (this.max - this.min)

      return { value, isInsideTrack }
    },
    parseKeyDown (e, value = this.internalValue) {
      if (this.disabled) return

      const { pageup, pagedown, end, home, left, right, down, up } = keyCodes

      if (![pageup, pagedown, end, home, left, right, down, up].includes(e.keyCode)) return

      e.preventDefault()
      const step = this.stepNumeric || 1
      const steps = (this.max - this.min) / step
      if ([left, right, down, up].includes(e.keyCode)) {
        this.keyPressed += 1

        const increase = this.$vuetify.rtl ? [left, up] : [right, up]
        let direction = increase.includes(e.keyCode) ? 1 : -1
        const multiplier = e.shiftKey ? 3 : (e.ctrlKey ? 2 : 1)

        value = value + (direction * step * multiplier)
      } else if (e.keyCode === home) {
        value = parseFloat(this.min)
      } else if (e.keyCode === end) {
        value = parseFloat(this.max)
      } else /* if (e.keyCode === keyCodes.pageup || e.keyCode === pagedown) */ {
        // Page up/down
        const direction = e.keyCode === pagedown ? 1 : -1
        value = value - (direction * step * (steps > 100 ? steps / 10 : 10))
      }

      return value
    },
    roundValue (value) {
      if (!this.stepNumeric) return value
      // Format input value using the same number
      // of decimals places as in the step prop
      const trimmedStep = this.step.toString().trim()
      const decimals = trimmedStep.indexOf('.') > -1
        ? (trimmedStep.length - trimmedStep.indexOf('.') - 1)
        : 0

      const newValue = Math.round(value / this.stepNumeric) * this.stepNumeric

      return parseFloat(Math.min(newValue, this.max).toFixed(decimals))
    },
    setInternalValue (value) {
      this.internalValue = value
    }
  }
}


import VCounter from './VCounter'

/* istanbul ignore next */
VCounter.install = function install (Vue) {
  Vue.component(VCounter.name, VCounter)
}

export { VCounter }
export default VCounter


import VBadge from '@/components/VBadge'
import { test } from '@/test'

test('VBadge.js', ({ mount, compileToFunctions }) => {
  it('should render component and match snapshot', async () => {
    const wrapper = mount(VBadge, {
      slots: {
        badge: [compileToFunctions('content')],
        default: [compileToFunctions('element')]
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with with value=false and match snapshot', async () => {
    const wrapper = mount(VBadge, {
      propsData: {
        value: false
      },
      slots: {
        badge: [compileToFunctions('content')],
        default: [compileToFunctions('element')]
      }
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bottom prop', () => {
    const wrapper = mount(VBadge, {
      propsData: {
        bottom: true
      }
    })

    expect(wrapper.hasClass('v-badge--bottom')).toBe(true)
  })

  it('should render component with left prop', () => {
    const wrapper = mount(VBadge, {
      propsData: {
        left: true
      }
    })

    expect(wrapper.hasClass('v-badge--left')).toBe(true)
  })

  it('should render component with overlap prop', () => {
    const wrapper = mount(VBadge, {
      propsData: {
        overlap: true
      }
    })

    expect(wrapper.hasClass('v-badge--overlap')).toBe(true)
  })

  it('should render component with color prop', () => {
    const wrapper = mount(VBadge, {
      propsData: {
        color: 'green lighten-1'
      },
      slots: {
        badge: [compileToFunctions('content')]
      }
    })

    const badge = wrapper.find('.v-badge__badge')[0]
    expect(badge.hasClass('green')).toBe(true)
    expect(badge.hasClass('lighten-1')).toBe(true)
  })
})


import { test } from '@/test'
import VSelect from '@/components/VSelect/VSelect'
import VChip from '@/components/VChip'

test('VSelect', ({ mount, compileToFunctions }) => {
  const app = document.createElement('div')
  app.setAttribute('data-app', true)
  document.body.appendChild(app)

  // Inspired by https://github.com/vuetifyjs/vuetify/pull/1425 - Thanks @kevmo314
  it('should open the select when focused and enter, space, up or down are pressed', async () => {
    const wrapper = mount(VSelect)

    wrapper.trigger('mouseup')

    expect(wrapper.vm.isMenuActive).toBe(false)

    wrapper.setProps({ box: true })
    wrapper.first('.v-input__slot').trigger('mouseup')

    expect(wrapper.vm.isMenuActive).toBe(true)

    wrapper.setData({ isMenuActive: false })
    wrapper.setProps({ box: false, solo: true })
    wrapper.first('.v-input__slot').trigger('mouseup')

    expect(wrapper.vm.isMenuActive).toBe(true)

    wrapper.setData({ isMenuActive: false })
    wrapper.setProps({ solo: false, soloInverted: true })
    wrapper.first('.v-input__slot').trigger('mouseup')

    expect(wrapper.vm.isMenuActive).toBe(true)

    wrapper.setData({ isMenuActive: false })
    wrapper.setProps({ soloInverted: false, outline: true })
    wrapper.first('.v-input__slot').trigger('mouseup')

    expect(wrapper.vm.isMenuActive).toBe(true)
  })

  it('should return full items if using auto prop', () => {
    const wrapper = mount(VSelect, {
      propsData: {
        items: [...Array(100).keys()]
      }
    })

    expect(wrapper.vm.virtualizedItems.length).toBe(20)

    wrapper.setProps({ auto: true })

    expect(wrapper.vm.virtualizedItems.length).toBe(100)
  })

  it('should fallback to using text as value if none present', () => {
    const wrapper = mount(VSelect, {
      propsData: {
        items: [{
          text: 'foo'
        }]
      }
    })

    expect(wrapper.vm.getValue(wrapper.vm.items[0])).toBe('foo')
  })

  it('should accept arrays as values', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        items: [
          { text: 'Foo', value: ['bar'] }
        ]
      }
    })

    const input = jest.fn()
    wrapper.vm.$on('input', input)

    wrapper.vm.selectItem(wrapper.vm.items[0])

    await wrapper.vm.$nextTick()

    expect(input).toBeCalledWith(['bar'])
    expect(wrapper.vm.selectedItems).toEqual([
      { text: 'Foo', value: ['bar'] }
    ])
  })

  // https://github.com/vuetifyjs/vuetify/issues/4359
  // Vue modifies the `on` property of the
  // computed `listData` — easiest way to fix
  it('should select value when using a scoped slot', async () => {
    const wrapper = mount(VSelect, {
      propsData: {
        items: ['foo', 'bar']
      },
      slots: {
        'no-data': [() => ({
          render: h => h('div', 'No Data')
        })]
      }
    })

    // Will be undefined if fails
    expect(wrapper.vm.listData.on).toBeTruthy()
  })
})