diff --git a/.vscode/settings.json b/.vscode/settings.json index 215a5f16b0f8a..a32489e34a647 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,10 @@ } ], "typescript.tsdk": "./node_modules/typescript/lib", - "cSpell.words": ["photoswipe", "tmid"] + "cSpell.words": [ + "livechat", + "omnichannel", + "photoswipe", + "tmid" + ] } diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartment.js b/apps/meteor/client/views/omnichannel/departments/EditDepartment.js index 25a62e6642626..da7349e73d886 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartment.js +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartment.js @@ -14,7 +14,7 @@ import { } from '@rocket.chat/fuselage'; import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useRoute, useMethod, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useMemo, useState, useRef } from 'react'; +import React, { useMemo, useState, useRef, useCallback } from 'react'; import { validateEmail } from '../../../../lib/emailValidator'; import Page from '../../../components/Page'; @@ -58,7 +58,9 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { const { department } = data || { department: {} }; - const [[tags, tagsText], setTagsState] = useState(() => [department?.chatClosingTags ?? [], '']); + const [initialTags] = useState(() => department?.chatClosingTags ?? []); + const [[tags, tagsText], setTagsState] = useState(() => [initialTags, '']); + const hasTagChanges = useMemo(() => tags.toString() !== initialTags.toString(), [tags, initialTags]); const { values, handlers, hasUnsavedChanges } = useForm({ name: withDefault(department?.name, ''), @@ -120,7 +122,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { setTagsState(([tags, tagsText]) => [tags.filter((_tag) => _tag !== tag), tagsText]); }; - const handleTagTextSubmit = useMutableCallback(() => { + const handleTagTextSubmit = useCallback(() => { setTagsState((state) => { const [tags, tagsText] = state; @@ -130,7 +132,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { return [[...tags, tagsText], '']; }); - }); + }, []); const handleTagTextChange = (e) => { setTagsState(([tags]) => [tags, e.target.value]); @@ -232,7 +234,11 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { }); const invalidForm = - !name || !email || !validateEmail(email) || !hasUnsavedChanges || (requestTagBeforeClosingChat && (!tags || tags.length === 0)); + !name || + !email || + !validateEmail(email) || + !(hasUnsavedChanges || hasTagChanges) || + (requestTagBeforeClosingChat && (!tags || tags.length === 0)); const formId = useUniqueId(); @@ -441,7 +447,13 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { onChange={handleTagTextChange} placeholder={t('Enter_a_tag')} /> - diff --git a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts index afa0f1b994f17..1075e3825dbc4 100644 --- a/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel-departaments.spec.ts @@ -1,51 +1,123 @@ import { faker } from '@faker-js/faker'; +import type { Page } from '@playwright/test'; import { test, expect } from './utils/test'; -import { OmnichannelDepartaments } from './page-objects'; +import { OmnichannelDepartments } from './page-objects'; test.use({ storageState: 'admin-session.json' }); -test.describe.serial('omnichannel-departaments', () => { - let poOmnichannelDepartaments: OmnichannelDepartaments; +test.describe.serial('omnichannel-departments', () => { + let poOmnichannelDepartments: OmnichannelDepartments; - const departmentName = faker.datatype.uuid(); + let departmentName: string; + test.beforeAll(() => { + departmentName = faker.datatype.uuid(); + }); - test.beforeEach(async ({ page }) => { - poOmnichannelDepartaments = new OmnichannelDepartaments(page); + test.beforeEach(async ({ page }: { page: Page }) => { + poOmnichannelDepartments = new OmnichannelDepartments(page); await page.goto('/omnichannel'); - await poOmnichannelDepartaments.sidenav.linkDepartments.click(); + await poOmnichannelDepartments.sidenav.linkDepartments.click(); }); test('expect create new department', async () => { - await poOmnichannelDepartaments.btnNew.click(); - await poOmnichannelDepartaments.btnEnabled.click(); - await poOmnichannelDepartaments.inputName.fill(departmentName); - await poOmnichannelDepartaments.inputEmail.fill(faker.internet.email()); - await poOmnichannelDepartaments.btnSave.click(); - - await poOmnichannelDepartaments.inputSearch.fill(departmentName); - await expect(poOmnichannelDepartaments.firstRowInTable).toBeVisible(); + await poOmnichannelDepartments.btnNew.click(); + await poOmnichannelDepartments.btnEnabled.click(); + await poOmnichannelDepartments.inputName.fill(departmentName); + await poOmnichannelDepartments.inputEmail.fill(faker.internet.email()); + await poOmnichannelDepartments.btnSave.click(); + + await poOmnichannelDepartments.inputSearch.fill(departmentName); + await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); }); test('expect update department name', async () => { - await poOmnichannelDepartaments.inputSearch.fill(departmentName); + await poOmnichannelDepartments.inputSearch.fill(departmentName); + + await poOmnichannelDepartments.firstRowInTable.locator(`text=${departmentName}`).click(); + await poOmnichannelDepartments.inputName.fill(`edited-${departmentName}`); + await poOmnichannelDepartments.btnSave.click(); + + await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); + }); + + test.describe('Tags', () => { + test.beforeEach(async () => { + await poOmnichannelDepartments.inputSearch.fill(departmentName); + await poOmnichannelDepartments.firstRowInTable.locator(`text=${departmentName}`).click(); + }); + + test('expect save form button be disabled', async () => { + await expect(poOmnichannelDepartments.btnSave).toBeDisabled(); + }); + + test('Disabled tags state', async () => { + await test.step('expect to have department tags toggle button', async () => { + await expect(poOmnichannelDepartments.toggleRequestTags).toBeVisible(); + }); + await test.step('expect have no add tag to department', async () => { + await expect(poOmnichannelDepartments.inputTags).not.toBeVisible(); + await expect(poOmnichannelDepartments.btnTagsAdd).not.toBeVisible(); + }); + }); - await poOmnichannelDepartaments.firstRowInTable.locator(`text=${departmentName}`).click(); - await poOmnichannelDepartaments.inputName.fill(`edited-${departmentName}`); - await poOmnichannelDepartaments.btnSave.click(); + test('Enabled tags state', async ({ page }) => { + await test.step('expect to have form save option disabled', async () => { + await expect(poOmnichannelDepartments.btnSave).toBeDisabled(); + }); - await poOmnichannelDepartaments.inputSearch.fill(`edited-${departmentName}`); - await expect(poOmnichannelDepartaments.firstRowInTable).toBeVisible(); + await test.step('expect clicking on toggle button to enable tags', async () => { + await poOmnichannelDepartments.toggleRequestTags.click(); + await expect(poOmnichannelDepartments.inputTags).toBeVisible(); + await expect(poOmnichannelDepartments.btnTagsAdd).toBeVisible(); + }); + await test.step('expect to be invalid if there is no tag added', async () => { + await expect(poOmnichannelDepartments.btnSave).toBeDisabled(); + await expect(poOmnichannelDepartments.invalidInputTags).toBeVisible(); + }); + + await test.step('expect to be not possible adding empty tags', async () => { + await poOmnichannelDepartments.inputTags.fill(''); + await expect(poOmnichannelDepartments.btnTagsAdd).toBeDisabled(); + }); + + await test.step('expect to have add and remove one tag properly tags', async () => { + const tagName = faker.datatype.string(5); + await poOmnichannelDepartments.inputTags.fill(tagName); + await poOmnichannelDepartments.btnTagsAdd.click(); + + await expect(page.locator(`button`, { hasText: tagName })).toBeVisible(); + + await expect(poOmnichannelDepartments.btnSave).toBeEnabled(); + + await page.locator(`button`, { hasText: tagName }).click(); + await expect(poOmnichannelDepartments.invalidInputTags).toBeVisible(); + await expect(poOmnichannelDepartments.btnSave).toBeDisabled(); + }); + await test.step('expect to not be possible adding same tag twice', async () => { + const tagName = faker.datatype.string(5); + await poOmnichannelDepartments.inputTags.fill(tagName); + await poOmnichannelDepartments.btnTagsAdd.click(); + await poOmnichannelDepartments.inputTags.fill(tagName); + await expect(poOmnichannelDepartments.btnTagsAdd).toBeDisabled(); + }); + }); }); - test('expect delete department', async () => { - await poOmnichannelDepartaments.inputSearch.fill(`edited-${departmentName}`); + test('expect delete department', async ({ page }) => { + await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); + + await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + + await page.waitForRequest('**/livechat/department**'); + + await poOmnichannelDepartments.btnDeleteFirstRowInTable.click(); + await poOmnichannelDepartments.btnModalConfirmDelete.click(); - await poOmnichannelDepartaments.btnDeletefirstRowInTable.click(); - await poOmnichannelDepartaments.btnModalConfirmDelete.click(); + await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); - await poOmnichannelDepartaments.inputSearch.fill(`edited-${departmentName}`); - await expect(poOmnichannelDepartaments.firstRowInTable).toBeHidden(); + await expect(poOmnichannelDepartments.firstRowInTable).toHaveCount(0); }); }); diff --git a/apps/meteor/tests/e2e/page-objects/index.ts b/apps/meteor/tests/e2e/page-objects/index.ts index 36608328880bc..4fc7a634e8d3a 100644 --- a/apps/meteor/tests/e2e/page-objects/index.ts +++ b/apps/meteor/tests/e2e/page-objects/index.ts @@ -5,5 +5,5 @@ export * from './auth'; export * from './home-channel'; export * from './home-discussion'; export * from './omnichannel-agents'; -export * from './omnichannel-departaments'; +export * from './omnichannel-departments'; export * from './omnichannel-livechat'; diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-departaments.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts similarity index 67% rename from apps/meteor/tests/e2e/page-objects/omnichannel-departaments.ts rename to apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts index b087e5ab1933a..2f61adc89d485 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-departaments.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts @@ -2,7 +2,7 @@ import type { Page } from '@playwright/test'; import { OmnichannelSidenav } from './fragments'; -export class OmnichannelDepartaments { +export class OmnichannelDepartments { private readonly page: Page; readonly sidenav: OmnichannelSidenav; @@ -32,6 +32,22 @@ export class OmnichannelDepartaments { return this.page.locator('[data-qa="DepartmentEditTextInput-Email"]'); } + get toggleRequestTags() { + return this.page.locator('[data-qa="DiscussionToggle-RequestTagBeforeCLosingChat"] span label'); + } + + get inputTags() { + return this.page.locator('[data-qa="DepartmentEditTextInput-ConversationClosingTags"]'); + } + + get invalidInputTags() { + return this.page.locator('[data-qa="DepartmentEditTextInput-ConversationClosingTags"]:invalid'); + } + + get btnTagsAdd() { + return this.page.locator('[data-qa="DepartmentEditAddButton-ConversationClosingTags"]'); + } + get btnSave() { return this.page.locator('button.rcx-button--primary.rcx-button >> text="Save"'); } @@ -40,7 +56,7 @@ export class OmnichannelDepartaments { return this.page.locator('table tr:first-child td:first-child'); } - get btnDeletefirstRowInTable() { + get btnDeleteFirstRowInTable() { return this.page.locator('table tr:first-child td:nth-child(6) button'); }