diff --git a/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.spec.tsx b/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.spec.tsx new file mode 100644 index 0000000000000..7be90d5250104 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.spec.tsx @@ -0,0 +1,21 @@ +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { composeStories } from '@storybook/react'; +import { render } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import * as stories from './ReturnChatQueueModal.stories'; + +const testCases = Object.values(composeStories(stories)).map((Story) => [Story.storyName || 'Story', Story]); + +const appRoot = mockAppRoot().build(); + +test.each(testCases)(`renders %s without crashing`, async (_storyname, Story) => { + const { baseElement } = render(, { wrapper: appRoot }); + expect(baseElement).toMatchSnapshot(); +}); + +test.each(testCases)('%s should have no a11y violations', async (_storyname, Story) => { + const { container } = render(, { wrapper: appRoot }); + const results = await axe(container); + expect(results).toHaveNoViolations(); +}); diff --git a/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.stories.tsx b/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.stories.tsx new file mode 100644 index 0000000000000..4d62542e92f20 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryFn } from '@storybook/react'; + +import ReturnChatQueueModal from './ReturnChatQueueModal'; + +export default { + component: ReturnChatQueueModal, + parameters: { + layout: 'fullscreen', + actions: { argTypesRegex: '^on.*' }, + }, + args: { + onMoveChat: () => undefined, + onCancel: () => undefined, + }, +} satisfies Meta; + +export const Default: StoryFn = (args) => ; diff --git a/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.tsx b/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.tsx index 09c912dec0d48..e35cadf5ecb4f 100644 --- a/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.tsx +++ b/apps/meteor/client/views/omnichannel/modals/ReturnChatQueueModal.tsx @@ -1,14 +1,4 @@ -import { - Button, - Modal, - ModalClose, - ModalContent, - ModalFooter, - ModalFooterControllers, - ModalHeader, - ModalIcon, - ModalTitle, -} from '@rocket.chat/fuselage'; +import { GenericModal } from '@rocket.chat/ui-client'; import { useTranslation } from 'react-i18next'; type ReturnChatQueueModalProps = { @@ -16,27 +6,20 @@ type ReturnChatQueueModalProps = { onCancel: () => void; }; -// TODO: use `GenericModal` instead of creating a new modal from scratch -const ReturnChatQueueModal = ({ onCancel, onMoveChat, ...props }: ReturnChatQueueModalProps) => { +const ReturnChatQueueModal = ({ onCancel, onMoveChat }: ReturnChatQueueModalProps) => { const { t } = useTranslation(); return ( - - - - {t('Return_to_the_queue')} - - - {t('Would_you_like_to_return_the_queue')} - - - {t('Cancel')} - - {t('Confirm')} - - - - + + {t('Would_you_like_to_return_the_queue')} + ); }; diff --git a/apps/meteor/client/views/omnichannel/modals/__snapshots__/ReturnChatQueueModal.spec.tsx.snap b/apps/meteor/client/views/omnichannel/modals/__snapshots__/ReturnChatQueueModal.spec.tsx.snap new file mode 100644 index 0000000000000..8de70012aa772 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/modals/__snapshots__/ReturnChatQueueModal.spec.tsx.snap @@ -0,0 +1,98 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`renders Default without crashing 1`] = ` + + + + + + + + + + + + + + Return_to_the_queue + + + + + + + + + + + + Would_you_like_to_return_the_queue + + + + + + + +`; diff --git a/apps/meteor/tests/end-to-end/api/custom-user-status.ts b/apps/meteor/tests/end-to-end/api/custom-user-status.ts index d157d79befa97..fab30b51fe33e 100644 --- a/apps/meteor/tests/end-to-end/api/custom-user-status.ts +++ b/apps/meteor/tests/end-to-end/api/custom-user-status.ts @@ -1,10 +1,14 @@ import { expect } from 'chai'; -import { after, before, describe, it } from 'mocha'; +import { after, afterEach, before, describe, it } from 'mocha'; +import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../data/api-data'; +import { updatePermission } from '../../data/permissions.helper'; +import { password } from '../../data/user'; +import { createUser, deleteUser, login } from '../../data/users.helper'; -async function createCustomUserStatus(name: string): Promise { - const res = await request.post(api('custom-user-status.create')).set(credentials).send({ name }).expect(200); +async function createCustomUserStatus(name: string, statusType?: string): Promise { + const res = await request.post(api('custom-user-status.create')).set(credentials).send({ name, statusType }).expect(200); return res.body.customUserStatus._id; } @@ -13,10 +17,20 @@ async function deleteCustomUserStatus(id: string): Promise { } describe('[CustomUserStatus]', () => { + let unauthorizedUser: any; + let unauthorizedUserCredentials: any; + before((done) => { getCredentials(done); }); + before(async () => { + unauthorizedUser = await createUser(); + unauthorizedUserCredentials = await login(unauthorizedUser.username, password); + }); + + after(() => Promise.all([updatePermission('manage-user-status', ['admin']), deleteUser(unauthorizedUser)])); + describe('[/custom-user-status.list]', () => { let customUserStatusId: string; let customUserStatusName: string; @@ -113,4 +127,285 @@ describe('[CustomUserStatus]', () => { .end(done); }); }); + + describe('[/custom-user-status.create]', () => { + let customUserStatusId: string; + + afterEach(async () => { + await updatePermission('manage-user-status', ['admin']); + + if (customUserStatusId) { + await deleteCustomUserStatus(customUserStatusId); + customUserStatusId = ''; + } + }); + + it('should throw an error if not authenticated', async () => { + await request + .post(api('custom-user-status.create')) + .send({ name: 'test-status' }) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res: Response) => { + expect(res.body).to.have.property('status', 'error'); + }); + }); + + it('should throw an error if user does not have manage-user-status permission', async () => { + await updatePermission('manage-user-status', []); + + await request + .post(api('custom-user-status.create')) + .set(unauthorizedUserCredentials) + .send({ name: 'test-status' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'not_authorized'); + }); + }); + + it('should create a custom user status successfully', async () => { + const statusName = `test-create-${Date.now()}`; + + await request + .post(api('custom-user-status.create')) + .set(credentials) + .send({ name: statusName, statusType: 'busy' }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('customUserStatus'); + expect(res.body.customUserStatus).to.have.property('_id'); + expect(res.body.customUserStatus).to.have.property('name', statusName); + expect(res.body.customUserStatus).to.have.property('statusType', 'busy'); + customUserStatusId = res.body.customUserStatus._id; + }); + }); + + it('should throw an error if name already exists', async () => { + const statusName = `test-duplicate-${Date.now()}`; + customUserStatusId = await createCustomUserStatus(statusName); + + await request + .post(api('custom-user-status.create')) + .set(credentials) + .send({ name: statusName }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'Custom_User_Status_Error_Name_Already_In_Use'); + }); + }); + + it('should throw an error if statusType is invalid', async () => { + await request + .post(api('custom-user-status.create')) + .set(credentials) + .send({ name: `test-invalid-status-type-${Date.now()}`, statusType: 'invalid' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-input-is-not-a-valid-field'); + }); + }); + }); + + describe('[/custom-user-status.update]', () => { + let customUserStatusId: string; + let customUserStatusName: string; + + before(async () => { + customUserStatusName = `test-update-${Date.now()}`; + customUserStatusId = await createCustomUserStatus(customUserStatusName); + }); + + afterEach(async () => { + await updatePermission('manage-user-status', ['admin']); + }); + + after(async () => { + if (customUserStatusId) { + await deleteCustomUserStatus(customUserStatusId); + } + await updatePermission('manage-user-status', ['admin']); + }); + + it('should throw an error if not authenticated', async () => { + await request + .post(api('custom-user-status.update')) + .send({ _id: customUserStatusId, name: 'updated-name' }) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res: Response) => { + expect(res.body).to.have.property('status', 'error'); + }); + }); + + it('should throw an error if user does not have manage-user-status permission', async () => { + await updatePermission('manage-user-status', []); + + await request + .post(api('custom-user-status.update')) + .set(unauthorizedUserCredentials) + .send({ _id: customUserStatusId, name: 'updated-name' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'not_authorized'); + }); + }); + + it('should throw an error if custom user status does not exist', async () => { + await request + .post(api('custom-user-status.update')) + .set(credentials) + .send({ _id: 'invalid-id', name: 'updated-name' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should update custom user status successfully', async () => { + const newName = `test-updated-${Date.now()}`; + + await request + .post(api('custom-user-status.update')) + .set(credentials) + .send({ _id: customUserStatusId, name: newName, statusType: 'away' }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('customUserStatus'); + expect(res.body.customUserStatus).to.have.property('_id', customUserStatusId); + expect(res.body.customUserStatus).to.have.property('name', newName); + expect(res.body.customUserStatus).to.have.property('statusType', 'away'); + }); + + customUserStatusName = newName; + }); + + it('should throw an error if status name already exists', async () => { + const existingStatusName = `test-update-duplicate-${Date.now()}`; + const existingStatusId = await createCustomUserStatus(existingStatusName); + + await request + .post(api('custom-user-status.update')) + .set(credentials) + .send({ _id: customUserStatusId, name: existingStatusName }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'Custom_User_Status_Error_Name_Already_In_Use'); + }); + + await deleteCustomUserStatus(existingStatusId); + }); + + it('should throw an error if statusType is invalid', async () => { + await request + .post(api('custom-user-status.update')) + .set(credentials) + .send({ _id: customUserStatusId, name: customUserStatusName, statusType: 'invalid' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-input-is-not-a-valid-field'); + }); + }); + }); + + describe('[/custom-user-status.delete]', () => { + let customUserStatusId: string; + + beforeEach(async () => { + const statusName = `test-delete-${Date.now()}`; + customUserStatusId = await createCustomUserStatus(statusName); + }); + + afterEach(async () => { + await updatePermission('manage-user-status', ['admin']); + + if (customUserStatusId) { + await deleteCustomUserStatus(customUserStatusId); + customUserStatusId = ''; + } + }); + + it('should throw an error if not authenticated', async () => { + await request + .post(api('custom-user-status.delete')) + .send({ customUserStatusId }) + .expect('Content-Type', 'application/json') + .expect(401) + .expect((res: Response) => { + expect(res.body).to.have.property('status', 'error'); + }); + }); + + it('should throw an error if user does not have manage-user-status permission', async () => { + await updatePermission('manage-user-status', []); + + await request + .post(api('custom-user-status.delete')) + .set(unauthorizedUserCredentials) + .send({ customUserStatusId }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'not_authorized'); + }); + }); + + it('should throw an error if customUserStatusId is not provided', async () => { + await request + .post(api('custom-user-status.delete')) + .set(credentials) + .send({}) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'The "customUserStatusId" params is required!'); + }); + }); + + it('should throw an error if custom user status does not exist', async () => { + await request + .post(api('custom-user-status.delete')) + .set(credentials) + .send({ customUserStatusId: 'invalid-id' }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res: Response) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'Custom_User_Status_Error_Invalid_User_Status'); + }); + }); + + it('should delete custom user status successfully', async () => { + await request + .post(api('custom-user-status.delete')) + .set(credentials) + .send({ customUserStatusId }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + + customUserStatusId = ''; + }); + }); });