diff --git a/.github/workflows/cypress-testing.yml b/.github/workflows/cypress-testing.yml index 6a77865be0..5d0b90ed0d 100644 --- a/.github/workflows/cypress-testing.yml +++ b/.github/workflows/cypress-testing.yml @@ -96,7 +96,7 @@ jobs: git clone https://github.com/glific/cypress-testing.git echo done. go to dir. cd cypress-testing - git checkout collections-management + git checkout main cd .. cp -r cypress-testing/cypress cypress yarn add cypress@13.6.2 diff --git a/src/containers/Form/FormLayout.tsx b/src/containers/Form/FormLayout.tsx index 6c27dbf678..acf268228c 100644 --- a/src/containers/Form/FormLayout.tsx +++ b/src/containers/Form/FormLayout.tsx @@ -240,7 +240,7 @@ export const FormLayout = ({ let itemUpdatedObject: any = Object.keys(data)[0]; itemUpdatedObject = data[itemUpdatedObject]; const updatedItem = itemUpdatedObject[listItem]; - const { errors } = itemUpdatedObject; + const { errors, message } = itemUpdatedObject; if (errors) { if (customHandler) { @@ -273,7 +273,7 @@ export const FormLayout = ({ } // emit data after save if (afterSave) { - afterSave(data, saveClick); + afterSave(data, saveClick, message); } } onSaveClick(false); diff --git a/src/containers/InteractiveMessage/InteractiveMessage.module.css b/src/containers/InteractiveMessage/InteractiveMessage.module.css index 4c90e9b083..1df09efeba 100644 --- a/src/containers/InteractiveMessage/InteractiveMessage.module.css +++ b/src/containers/InteractiveMessage/InteractiveMessage.module.css @@ -11,3 +11,7 @@ .Simulator > div { top: 200px !important; } + +.DialogContent { + text-align: center; +} diff --git a/src/containers/InteractiveMessage/InteractiveMessage.test.tsx b/src/containers/InteractiveMessage/InteractiveMessage.test.tsx index e6356d4ff3..2a1498af76 100644 --- a/src/containers/InteractiveMessage/InteractiveMessage.test.tsx +++ b/src/containers/InteractiveMessage/InteractiveMessage.test.tsx @@ -7,10 +7,15 @@ import { Route, MemoryRouter, Routes } from 'react-router-dom'; import { vi } from 'vitest'; import { setUserSession } from 'services/AuthService'; -import { mocks } from 'mocks/InteractiveMessage'; +import { + mocks, + translateInteractiveTemplateMock, + translateWitTrimmingMocks, + translateWithoutTrimmingMocks, +} from 'mocks/InteractiveMessage'; import { InteractiveMessage } from './InteractiveMessage'; import { FLOW_EDITOR_API } from 'config'; -import { setNotification } from 'common/notification'; +import { setErrorMessage, setNotification } from 'common/notification'; afterEach(() => { cleanup(); @@ -31,6 +36,7 @@ const mockUseLocationValue: any = { hash: '', state: null, }; + vi.mock('react-router-dom', async () => ({ ...((await vi.importActual('react-router-dom')) as {}), useLocation: () => { @@ -44,6 +50,7 @@ vi.mock('common/notification', async (importOriginal) => { return { ...mod, setNotification: vi.fn(), + setErrorMessage: vi.fn(), }; }); @@ -80,23 +87,36 @@ const mockData = [...mocks, ...mocks]; setUserSession(JSON.stringify({ organization: { id: '1' }, roles: ['Admin'] })); -const renderInteractiveMessage = (id: string) => ( - - - - } /> - - - -); - -const interactiveMessage = ( - - - - - -); +const renderInteractiveMessage = (id: string, mocks?: any) => { + let MOCKS = mockData; + if (mocks) { + MOCKS = [...MOCKS, ...mocks]; + } + return ( + + + + } /> + + + + ); +}; + +const interactiveMessage = (mock?: any) => { + let MOCKS = mockData; + if (mock) { + MOCKS = [...MOCKS, ...mock]; + } + + return ( + + + + + + ); +}; const fieldsMock = { results: [{ key: 'key 1' }, { key: 'key 2' }], @@ -142,184 +162,207 @@ vi.spyOn(axios, 'get').mockImplementation((url: string) => { } }); -test('it renders empty interactive form', async () => { - render(interactiveMessage); +describe('Add mode', () => { + test('it renders empty interactive form', async () => { + render(interactiveMessage()); - // Adding another quick reply button - await waitFor(() => { - expect(screen.getByTestId('addButton')).toBeInTheDocument(); - }); + // Adding another quick reply button + await waitFor(() => { + expect(screen.getByTestId('addButton')).toBeInTheDocument(); + }); - const addQuickReplyButton = screen.getByTestId('addButton'); - fireEvent.click(addQuickReplyButton); + const addQuickReplyButton = screen.getByTestId('addButton'); + fireEvent.click(addQuickReplyButton); - await waitFor(() => { - // Get all input elements - const [title, lexicalEditor, quickReply1, quickReply2, , attachmentUrl] = - screen.getAllByRole('textbox'); - expect(title).toBeInTheDocument(); - expect(quickReply1).toBeInTheDocument(); - expect(quickReply2).toBeInTheDocument(); - expect(attachmentUrl).toBeInTheDocument(); + await waitFor(() => { + // Get all input elements + const [title, lexicalEditor, quickReply1, quickReply2, , attachmentUrl] = + screen.getAllByRole('textbox'); + expect(title).toBeInTheDocument(); + expect(quickReply1).toBeInTheDocument(); + expect(quickReply2).toBeInTheDocument(); + expect(attachmentUrl).toBeInTheDocument(); + + fireEvent.change(title, { target: { value: 'new title' } }); + userEvent.click(lexicalEditor); + userEvent.keyboard('Yes'); + fireEvent.change(quickReply1, { target: { value: 'Yes' } }); + fireEvent.change(quickReply2, { target: { value: 'No' } }); + fireEvent.change(attachmentUrl, { target: { value: 'https://picsum.photos/200/300' } }); + fireEvent.blur(attachmentUrl); + }); - fireEvent.change(title, { target: { value: 'new title' } }); - userEvent.click(lexicalEditor); - userEvent.keyboard('Yes'); - fireEvent.change(quickReply1, { target: { value: 'Yes' } }); - fireEvent.change(quickReply2, { target: { value: 'No' } }); - fireEvent.change(attachmentUrl, { target: { value: 'https://picsum.photos/200/300' } }); - fireEvent.blur(attachmentUrl); - }); + // // Changing language to marathi + await waitFor(() => { + expect(screen.getByText('Marathi')).toBeInTheDocument(); + }); - // // Changing language to marathi - await waitFor(() => { - expect(screen.getByText('Marathi')).toBeInTheDocument(); - }); + const language = screen.getByText('Marathi'); + fireEvent.click(language); - const language = screen.getByText('Marathi'); - fireEvent.click(language); + await waitFor(() => { + const [interactiveType] = screen.getAllByTestId('autocomplete-element'); + expect(interactiveType).toBeInTheDocument(); + }); - await waitFor(() => { + // Switiching to list const [interactiveType] = screen.getAllByTestId('autocomplete-element'); - expect(interactiveType).toBeInTheDocument(); - }); + interactiveType.focus(); + fireEvent.keyDown(interactiveType, { key: 'ArrowDown' }); + fireEvent.keyDown(interactiveType, { key: 'ArrowDown' }); + fireEvent.keyDown(interactiveType, { key: 'Enter' }); - // Switiching to list - const [interactiveType] = screen.getAllByTestId('autocomplete-element'); - interactiveType.focus(); - fireEvent.keyDown(interactiveType, { key: 'ArrowDown' }); - fireEvent.keyDown(interactiveType, { key: 'ArrowDown' }); - fireEvent.keyDown(interactiveType, { key: 'Enter' }); - - await waitFor(() => { - // Adding list data - const [, , header, listTitle, listItemTitle, listItemDesc] = screen.getAllByRole('textbox'); - - expect(header).toBeInTheDocument(); - expect(listTitle).toBeInTheDocument(); - expect(listItemTitle).toBeInTheDocument(); - expect(listItemDesc).toBeInTheDocument(); - - fireEvent.change(header, { target: { value: 'Section 1' } }); - fireEvent.blur(header); - fireEvent.change(listTitle, { target: { value: 'title' } }); - fireEvent.change(listItemTitle, { target: { value: 'red' } }); - fireEvent.change(listItemDesc, { target: { value: 'red is color' } }); - }); + await waitFor(() => { + // Adding list data + const [, , header, listTitle, listItemTitle, listItemDesc] = screen.getAllByRole('textbox'); + + expect(header).toBeInTheDocument(); + expect(listTitle).toBeInTheDocument(); + expect(listItemTitle).toBeInTheDocument(); + expect(listItemDesc).toBeInTheDocument(); + + fireEvent.change(header, { target: { value: 'Section 1' } }); + fireEvent.blur(header); + fireEvent.change(listTitle, { target: { value: 'title' } }); + fireEvent.change(listItemTitle, { target: { value: 'red' } }); + fireEvent.change(listItemDesc, { target: { value: 'red is color' } }); + }); - await waitFor(() => { - // Adding another list item - const addAnotherListItemButton = screen.getByText('Add item'); - expect(addAnotherListItemButton); - fireEvent.click(addAnotherListItemButton); - }); + await waitFor(() => { + // Adding another list item + const addAnotherListItemButton = screen.getByText('Add item'); + expect(addAnotherListItemButton); + fireEvent.click(addAnotherListItemButton); + }); - await waitFor(() => { - // Adding another list - const addAnotherListButton = screen.getByText('Add list'); - expect(addAnotherListButton); - fireEvent.click(addAnotherListButton); - }); + await waitFor(() => { + // Adding another list + const addAnotherListButton = screen.getByText('Add list'); + expect(addAnotherListButton); + fireEvent.click(addAnotherListButton); + }); - await waitFor(() => { - expect(screen.getAllByTestId('delete-icon')).toHaveLength(2); - }); - // Deleting list - const deleteListButton = screen.getAllByTestId('delete-icon')[1]; - fireEvent.click(deleteListButton); - await waitFor(() => { - // Deleting list item - const deleteListItemButton = screen.getByTestId('cross-icon'); - expect(deleteListItemButton).toBeInTheDocument(); - fireEvent.click(deleteListItemButton); - }); + await waitFor(() => { + expect(screen.getAllByTestId('delete-icon')).toHaveLength(2); + }); + // Deleting list + const deleteListButton = screen.getAllByTestId('delete-icon')[1]; + fireEvent.click(deleteListButton); + await waitFor(() => { + // Deleting list item + const deleteListItemButton = screen.getByTestId('cross-icon'); + expect(deleteListItemButton).toBeInTheDocument(); + fireEvent.click(deleteListItemButton); + }); - // Fill Message field with an emoji (as it's a required field) - await userEvent.click(screen.getByTestId('emoji-picker')); - const emojiContainer = screen.getByTestId('emoji-container'); - await userEvent.click(emojiContainer); + // Fill Message field with an emoji (as it's a required field) + await userEvent.click(screen.getByTestId('emoji-picker')); + const emojiContainer = screen.getByTestId('emoji-container'); + await userEvent.click(emojiContainer); - await waitFor(() => { - const saveButton = screen.getByText('Save'); - expect(saveButton).toBeInTheDocument(); - fireEvent.click(saveButton); - }); + await waitFor(() => { + const saveButton = screen.getByText('Save'); + expect(saveButton).toBeInTheDocument(); + fireEvent.click(saveButton); + }); - // successful save - await waitFor(() => { - expect(setNotification).toHaveBeenCalledWith('Interactive message created successfully!'); + // successful save + await waitFor(() => { + expect(setNotification).toHaveBeenCalledWith('Interactive message created successfully!'); + }); }); -}); -test('it renders interactive quick reply in edit mode', async () => { - render(renderInteractiveMessage('1')); + test('it validates url', async () => { + const { getByText, getAllByRole } = render(interactiveMessage()); - await waitFor(() => { - // Changing language to marathi to see translations - expect(screen.getByText('Marathi')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(getByText('Add a new Interactive message')).toBeInTheDocument(); + }); - const marathi = screen.getByText('Marathi'); - fireEvent.click(marathi); + const autoCompletes = getAllByRole('combobox'); - await waitFor(() => { - expect(screen.getByText('English')).toBeInTheDocument(); - }); - // Changing back to English - const english = screen.getByText('English'); - fireEvent.click(english); + const attachmentType = autoCompletes[1]; - await waitFor(() => { - expect(screen.getByText('Save')).toBeInTheDocument(); - }); - const saveButton = screen.getByText('Save'); - fireEvent.click(saveButton); + attachmentType.focus(); + fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); + fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); + fireEvent.keyDown(attachmentType, { key: 'Enter' }); - await waitFor(() => { - expect(screen.getByText('Navigated to /interactive-message')).toBeInTheDocument(); + fireEvent.change(getAllByRole('textbox')[4], { target: { value: 'bhhdhds' } }); }); -}); -test('it renders interactive list in edit mode', async () => { - render(renderInteractiveMessage('2')); + test('It creates a interactive message with dynamic content', async () => { + const { getByTestId, getAllByRole, getByText } = render(interactiveMessage()); + await waitFor(() => { + expect(getByText('Marathi')).toBeInTheDocument(); + }); + + fireEvent.click(getAllByRole('checkbox')[1]); + + const autoCompletes = getAllByRole('combobox'); - // vi.spyOn(axios, 'get').mockResolvedValueOnce(responseMock1); + const attachmentType = autoCompletes[1]; - await waitFor(() => { - const saveButton = screen.getByText('Save'); - expect(saveButton).toBeInTheDocument(); - fireEvent.click(saveButton); + attachmentType.focus(); + fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); + fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); + fireEvent.keyDown(attachmentType, { key: 'Enter' }); + + fireEvent.change(getAllByRole('textbox')[4], { target: { value: '@results.result_1' } }); + fireEvent.click(getByTestId('submitActionButton')); }); }); -test('it renders interactive quick reply with media in edit mode', async () => { - render(renderInteractiveMessage('3')); +describe('Edit mode', () => { + test('it renders quick reply in edit mode and changes language', async () => { + render(renderInteractiveMessage('1')); - // vi.spyOn(axios, 'get').mockResolvedValueOnce(responseMock3); -}); + await waitFor(() => { + expect(screen.getByText('Title')).toBeInTheDocument(); + expect(screen.getByText('Details Confirmation')).toBeInTheDocument(); + expect(screen.getByText('Marathi')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByText('Marathi')); + + await waitFor(() => { + expect(screen.getByText('Title')).toBeInTheDocument(); + expect(screen.getByText('सही')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByText('English')); + + await waitFor(() => { + expect(screen.getByText('Details Confirmation')).toBeInTheDocument(); + }); -test('it validates url', async () => { - const { getByText, getAllByRole } = render(interactiveMessage); + fireEvent.click(screen.getByTestId('submitActionButton')); - await waitFor(() => { - expect(getByText('Add a new Interactive message')).toBeInTheDocument(); + await waitFor(() => { + expect(setNotification).toHaveBeenCalled(); + }); }); - const autoCompletes = getAllByRole('combobox'); + test('it renders interactive list in edit mode', async () => { + render(renderInteractiveMessage('2')); - const attachmentType = autoCompletes[1]; + await waitFor(() => { + const saveButton = screen.getByText('Save'); + expect(saveButton).toBeInTheDocument(); + fireEvent.click(saveButton); + }); + }); - attachmentType.focus(); - fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); - fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); - fireEvent.keyDown(attachmentType, { key: 'Enter' }); + test('it renders interactive quick reply with media in edit mode', async () => { + render(renderInteractiveMessage('3')); - fireEvent.change(getAllByRole('textbox')[4], { target: { value: 'bhhdhds' } }); + // vi.spyOn(axios, 'get').mockResolvedValueOnce(responseMock3); + }); }); describe('location request message', () => { test('it renders empty location request message', async () => { - render(interactiveMessage); + render(interactiveMessage()); await waitFor(() => { expect(screen.getAllByTestId('autocomplete-element')[0]).toBeInTheDocument(); @@ -350,30 +393,9 @@ describe('location request message', () => { }); }); -test('It creates a interactive message with dynamic content', async () => { - const { getByTestId, getAllByRole, getByText } = render(interactiveMessage); - await waitFor(() => { - expect(getByText('Marathi')).toBeInTheDocument(); - }); - - fireEvent.click(getAllByRole('checkbox')[1]); - - const autoCompletes = getAllByRole('combobox'); - - const attachmentType = autoCompletes[1]; - - attachmentType.focus(); - fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); - fireEvent.keyDown(attachmentType, { key: 'ArrowDown' }); - fireEvent.keyDown(attachmentType, { key: 'Enter' }); - - fireEvent.change(getAllByRole('textbox')[4], { target: { value: '@results.result_1' } }); - fireEvent.click(getByTestId('submitActionButton')); -}); - describe('translates the template', () => { test('it shows error if clicked on translation without filling details', async () => { - const { getByText } = render(interactiveMessage); + const { getByText } = render(interactiveMessage(translateWithoutTrimmingMocks)); await waitFor(() => { expect(getByText('Add a new Interactive message')).toBeInTheDocument(); @@ -393,7 +415,7 @@ describe('translates the template', () => { }); test('it translates a new template', async () => { - const { getByText } = render(interactiveMessage); + const { getByText } = render(interactiveMessage(translateWithoutTrimmingMocks)); await waitFor(() => { expect(getByText('Add a new Interactive message')).toBeInTheDocument(); @@ -428,7 +450,7 @@ describe('translates the template', () => { }); test('it translates an already exisiting template', async () => { - render(renderInteractiveMessage('1')); + render(renderInteractiveMessage('1', translateWithoutTrimmingMocks)); await waitFor(() => { expect(screen.getByText('Edit Interactive message')).toBeInTheDocument(); @@ -440,7 +462,7 @@ describe('translates the template', () => { expect(screen.getByText('Translate Options')).toBeInTheDocument(); }); - fireEvent.click(screen.getByText('Translate Interactive Message')); + fireEvent.click(screen.getByText('Auto translate')); fireEvent.click(screen.getByText('Continue')); @@ -448,6 +470,72 @@ describe('translates the template', () => { expect(setNotification).toHaveBeenCalled(); }); }); + + test('it translates an already exisiting template', async () => { + render(renderInteractiveMessage('1', [translateInteractiveTemplateMock(true)])); + + await waitFor(() => { + expect(screen.getByText('Edit Interactive message')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByTestId('translateBtn')); + + await waitFor(() => { + expect(screen.getByText('Translate Options')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByText('Auto translate')); + + fireEvent.click(screen.getByText('Continue')); + + await waitFor(() => { + expect(setErrorMessage).toHaveBeenCalled(); + }); + }); + + test('it translates an already exisiting template with trimming', async () => { + render(renderInteractiveMessage('1', translateWitTrimmingMocks)); + + await waitFor(() => { + expect(screen.getByText('Edit Interactive message')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByTestId('translateBtn')); + + await waitFor(() => { + expect(screen.getByText('Translate Options')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByText('Auto translate')); + + fireEvent.click(screen.getByText('Continue')); + + await waitFor(() => { + expect(screen.getByText('Translations exceeding limit.')).toBeInTheDocument(); + }); + }); + + test('it shows warning if contents are trimmed', async () => { + render(renderInteractiveMessage('4')); + + await waitFor(() => { + expect(screen.getByText('Title')).toBeInTheDocument(); + expect(screen.getByText('Details Confirmation')).toBeInTheDocument(); + expect(screen.getByText('Marathi')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByText('Marathi')); + + await waitFor(() => { + expect(screen.getByText('Details Confirmation')).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getByText('Translations exceeding limit.')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByTestId('ok-button')); + }); }); describe('copy interactive message', () => { @@ -460,7 +548,7 @@ describe('copy interactive message', () => { await waitFor(() => { expect(getByText('Copy Interactive Message')).toBeInTheDocument(); const input = getAllByTestId('input'); - expect(input[0]?.querySelector('input')).toHaveValue('Copy of Continue'); + expect(input[0]?.querySelector('input')).toHaveValue('Copy of Details Confirmation'); }); }); }); diff --git a/src/containers/InteractiveMessage/InteractiveMessage.tsx b/src/containers/InteractiveMessage/InteractiveMessage.tsx index c90b6be4d7..f201043e1b 100644 --- a/src/containers/InteractiveMessage/InteractiveMessage.tsx +++ b/src/containers/InteractiveMessage/InteractiveMessage.tsx @@ -43,6 +43,7 @@ import { GET_TAGS } from 'graphql/queries/Tags'; import { CreateAutoComplete } from 'components/UI/Form/CreateAutoComplete/CreateAutoComplete'; import { interactiveMessageInfo } from 'common/HelpData'; import { TranslateButton } from './TranslateButton/TranslateButton'; +import { DialogBox } from 'components/UI/DialogBox/DialogBox'; const interactiveMessageIcon = ( @@ -89,6 +90,8 @@ export const InteractiveMessage = () => { const [previousState, setPreviousState] = useState({}); const [nextLanguage, setNextLanguage] = useState(''); + const [translateMessage, setTranslateMessage] = useState(null); + const { t } = useTranslation(); const params = useParams(); let isEditing = false; @@ -444,7 +447,7 @@ export const InteractiveMessage = () => { } }; - const afterSave = (data: any, saveClick: boolean) => { + const afterSave = (data: any, saveClick: boolean, message?: any) => { if (!saveClick) { if (params.id) { handleLanguageChange(nextLanguage); @@ -454,6 +457,10 @@ export const InteractiveMessage = () => { state: { language: nextLanguage }, }); } + + if (message) { + setTranslateMessage(message); + } } }; @@ -844,6 +851,21 @@ export const InteractiveMessage = () => { attachmentURL, ]); + let messageDialog; + if (translateMessage) { + messageDialog = ( + setTranslateMessage(null)} + skipCancel + > +
{translateMessage}
+
+ ); + } + if (languageOptions.length < 1 || loadingTemplate || tagsLoading) { return ; } @@ -882,6 +904,7 @@ export const InteractiveMessage = () => { simulatorIcon={false} /> + {translateMessage && messageDialog} ); }; diff --git a/src/containers/InteractiveMessage/TranslateButton/TranslateButton.test.tsx b/src/containers/InteractiveMessage/TranslateButton/TranslateButton.test.tsx index 0d99e22206..9899d30dd6 100644 --- a/src/containers/InteractiveMessage/TranslateButton/TranslateButton.test.tsx +++ b/src/containers/InteractiveMessage/TranslateButton/TranslateButton.test.tsx @@ -5,6 +5,7 @@ import { exportInteractiveTemplateMock, exportInteractiveTemplateMockWithoutTranslation, importInteractiveTemplateMock, + importInteractiveTemplateWithTrimmingMock, translateInteractiveTemplateMock, } from 'mocks/InteractiveMessage'; import { TranslateButton } from './TranslateButton'; @@ -22,13 +23,13 @@ const defaultProps = { saveClicked: false, }; -const wrapper = (props?: any) => ( +const wrapper = (props?: any, mocks: any = []) => ( @@ -58,7 +59,7 @@ test('it exports the translation with translations', async () => { expect(screen.getByText('Translate Options')).toBeInTheDocument(); }); - fireEvent.click(screen.getByText('Export Interactive Template With Translations')); + fireEvent.click(screen.getByText('Export with translations')); fireEvent.click(screen.getByText('Continue')); @@ -77,7 +78,7 @@ test('it exports the translation without translations', async () => { expect(screen.getByText('Translate Options')).toBeInTheDocument(); }); - fireEvent.click(screen.getByText('Export Interactive Template Without Translations')); + fireEvent.click(screen.getByText('Export without translations')); fireEvent.click(screen.getByText('Continue')); @@ -87,7 +88,7 @@ test('it exports the translation without translations', async () => { }); test('it imports the template', async () => { - render(wrapper({ saveClicked: false })); + render(wrapper({ saveClicked: false }, [importInteractiveTemplateMock()])); fireEvent.click(screen.getByText('Translate')); @@ -95,7 +96,7 @@ test('it imports the template', async () => { expect(screen.getByText('Translate Options')).toBeInTheDocument(); }); - fireEvent.click(screen.getByText('Import Interactive Template')); + fireEvent.click(screen.getByText('Import translations')); const file = new File(['content'], 'template.csv', { type: 'text/csv' }); const input = screen.getByTestId('import'); @@ -126,7 +127,7 @@ test('should throw error for failed translations', async () => { expect(screen.getByText('Translate Options')).toBeInTheDocument(); }); - fireEvent.click(screen.getByText('Export Interactive Template Without Translations')); + fireEvent.click(screen.getByText('Export without translations')); fireEvent.click(screen.getByText('Continue')); @@ -144,7 +145,7 @@ test('should throw error for failed import', async () => { expect(screen.getByText('Translate Options')).toBeInTheDocument(); }); - fireEvent.click(screen.getByText('Import Interactive Template')); + fireEvent.click(screen.getByText('Import translations')); const file = new File(['content'], 'template.csv', { type: 'text/csv' }); const input = screen.getByTestId('import'); @@ -154,3 +155,23 @@ test('should throw error for failed import', async () => { expect(errormessagespy).toHaveBeenCalled(); }); }); + +test('it should show warning if message exceeds limit', async () => { + render(wrapper({ saveClicked: false }, [importInteractiveTemplateWithTrimmingMock])); + + fireEvent.click(screen.getByText('Translate')); + + await waitFor(() => { + expect(screen.getByText('Translate Options')).toBeInTheDocument(); + }); + + fireEvent.click(screen.getByText('Import translations')); + + const file = new File(['content'], 'template.csv', { type: 'text/csv' }); + const input = screen.getByTestId('import'); + fireEvent.change(input, { target: { files: [file] } }); + + await waitFor(() => { + expect(screen.getByText('Translations exceeding limit.')).toBeInTheDocument(); + }); +}); diff --git a/src/containers/InteractiveMessage/TranslateButton/TranslateButton.tsx b/src/containers/InteractiveMessage/TranslateButton/TranslateButton.tsx index 031d158d61..3ce13744b2 100644 --- a/src/containers/InteractiveMessage/TranslateButton/TranslateButton.tsx +++ b/src/containers/InteractiveMessage/TranslateButton/TranslateButton.tsx @@ -40,29 +40,30 @@ export const TranslateButton = ({ const [showTranslateModal, setShowTranslateModal] = useState(false); const [translateOption, setTranslateOption] = useState('translate'); const [importing, setImporting] = useState(false); + const [translateMessage, setTranslateMessage] = useState(null); const { t } = useTranslation(); const translationOptions = [ { value: 'translate', - label: t('Translate Interactive Message'), - description: t('Translate the content of the Interactive Message.'), + label: t('Auto translate'), + description: t('Translate the content of interactive message.'), }, { value: 'export-translate', - label: t('Export Interactive Template With Translations'), - description: t('Export the translated content of templates as a csv.'), + label: t('Export with translations'), + description: t('Export the translated content of the message as a csv.'), }, { value: 'export', - label: t('Export Interactive Template Without Translations'), - description: t('Export the content without any translations.'), + label: t('Export without translations'), + description: t('Export the content of the message as a csv.'), }, { value: 'import', - label: t('Import Interactive Template'), - description: t('Import templates from a CSV file into the application.'), + label: t('Import translations'), + description: t('Import the csv with translations for interactive message.'), }, ]; @@ -74,9 +75,15 @@ export const TranslateButton = ({ const [translateInteractiveMessage, { loading }] = useMutation(TRANSLATE_INTERACTIVE_TEMPLATE, { onCompleted: ({ translateInteractiveTemplate }: any) => { - const interactiveMessage = translateInteractiveTemplate?.interactiveTemplate; - setStates(interactiveMessage); - setNotification('Interactive Message Translated Successfully', 'success'); + const { interactiveTemplate, message } = translateInteractiveTemplate; + setStates(interactiveTemplate); + + if (message) { + setTranslateMessage(message); + } else { + setNotification('Interactive Message Translated Successfully', 'success'); + } + handleClose(); }, onError(error: any) { @@ -105,9 +112,14 @@ export const TranslateButton = ({ IMPORT_INTERACTIVE_TEMPLATE, { onCompleted: ({ importInteractiveTemplate }) => { - const interactiveMessage = importInteractiveTemplate?.interactiveTemplate; - setNotification('Interactive Message Imported Successfully!', 'success'); - setStates(interactiveMessage); + const { interactiveTemplate, message } = importInteractiveTemplate; + setStates(interactiveTemplate); + + if (message) { + setTranslateMessage(message); + } else { + setNotification('Interactive Message Imported Successfully!', 'success'); + } handleClose(); }, onError: (error: any) => { @@ -215,6 +227,21 @@ export const TranslateButton = ({ ); + let messageDialog; + if (translateMessage) { + messageDialog = ( + setTranslateMessage(null)} + skipCancel + > +
{translateMessage}
+
+ ); + } + return (
{showTranslateModal ? dialog : ''} + {translateMessage && messageDialog}
); }; diff --git a/src/graphql/mutations/InteractiveMessage.ts b/src/graphql/mutations/InteractiveMessage.ts index dae8b4aa87..3fbe7e8127 100644 --- a/src/graphql/mutations/InteractiveMessage.ts +++ b/src/graphql/mutations/InteractiveMessage.ts @@ -25,6 +25,7 @@ export const CREATE_INTERACTIVE = gql` export const UPDATE_INTERACTIVE = gql` mutation updateInteractiveTemplate($id: ID!, $input: InteractiveTemplateInput!) { updateInteractiveTemplate(id: $id, input: $input) { + message interactiveTemplate { id label @@ -79,6 +80,7 @@ export const COPY_INTERACTIVE = gql` export const TRANSLATE_INTERACTIVE_TEMPLATE = gql` mutation TranslateInteractiveTemplate($translateInteractiveTemplateId: ID!) { translateInteractiveTemplate(id: $translateInteractiveTemplateId) { + message errors { key message @@ -114,6 +116,7 @@ export const EXPORT_INTERACTIVE_TEMPLATE = gql` export const IMPORT_INTERACTIVE_TEMPLATE = gql` mutation ImportInteractiveTemplate($importInteractiveTemplateId: ID!, $translation: String) { importInteractiveTemplate(id: $importInteractiveTemplateId, translation: $translation) { + message errors { key message diff --git a/src/i18n/en/en.json b/src/i18n/en/en.json index 64c9172e4c..c9bd01044d 100644 --- a/src/i18n/en/en.json +++ b/src/i18n/en/en.json @@ -502,12 +502,11 @@ "Group has been removed successfully from the collection.": "Group has been removed successfully from the collection.", "Contact has been added successfully to the collection.": "Contact has been added successfully to the collection.", "Contact has been removed successfully from the collection.": "Contact has been removed successfully from the collection.", - "Translate Interactive Message": "Translate Interactive Message", - "Translate the content of the Interactive Message.": "Translate the content of the Interactive Message.", - "Export Interactive Template Without Translations": "Export Interactive Template Without Translations", - "Export Interactive Template With Translations": "Export Interactive Template With Translations", - "Export the translated content of templates as a csv.": "Export the translated content of templates as a csv.", - "Export the content without any translations.": "Export the content without any translations.", - "Import Interactive Template": "Import Interactive Template", - "Import templates from a CSV file into the application.": "Import templates from a CSV file into the application." + "Auto translate": "Auto translate", + "Translate the content of interactive message.": "Translate the content of interactive message.", + "Export with translations": "Export with translations", + "Export without translations": "Export without translations", + "Export the translated content of the message as a csv.": "Export the translated content of the message as a csv.", + "Export the content of the message as a csv.": "Export the content of the message as a csv.", + "Import the csv with translations for interactive message.": "Import the csv with translations for interactive message." } diff --git a/src/mocks/InteractiveMessage.tsx b/src/mocks/InteractiveMessage.tsx index a158fb5ec8..9bef88bc5e 100644 --- a/src/mocks/InteractiveMessage.tsx +++ b/src/mocks/InteractiveMessage.tsx @@ -144,33 +144,41 @@ export const getInteractiveCountQuery = { }; const quickReplyMock = { - sendWithTitle: false, + id: '1', interactiveContent: - '{"type":"quick_reply","options":[{"type":"text","title":"Yes"},{"type":"text","title":"No"}],"content":{"type":"text","text":"Do you want to continue?","header":"Continue"}}', - label: 'Continue', - translations: - '{"1":{"type":"quick_reply","options":[{"type":"text","title":"Yes"},{"type":"text","title":"No"}],"content":{"type":"text","text":"Do you want to continue?","header":"Continue"}}}', - type: 'QUICK_REPLY', + '{"type":"quick_reply","options":[{"type":"text","title":"Correct"},{"type":"text","title":"Re-enter details"}],"content":{"type":"text","text":"Please *confirm* if the below details are correct-\\n\\n*Name:* @results.name\\n*Profile of:* @results.role","header":"Details Confirmation"}}', + label: 'Details Confirmation', language: { + __typename: 'Language', id: '1', label: 'English', }, - tag: { - id: '1', - label: 'New tag', - }, + sendWithTitle: true, + tag: null, + translations: + '{"2":{"type":"quick_reply","options":[{"type":"text","title":"सही"},{"type":"text","title":"विवरण प"}],"content":{"type":"text","text":"कृपया पुष्टि करें कि नीचे दी गई जानकारी सही है या नहीं-\\n\\n*नाम:* @results.name\\n*प्रोफ़ाइल:* @results.role","header":"विवरण पुष्टि"}},"1":{"type":"quick_reply","options":[{"type":"text","title":"Correct"},{"type":"text","title":"Re-enter details"}],"content":{"type":"text","text":"Please *confirm* if the below details are correct-\\n\\n*Name:* @results.name\\n*Profile of:* @results.role","header":"Details Confirmation"}}}', + type: 'QUICK_REPLY', }; const quickReplyMockInput = { type: 'QUICK_REPLY', interactiveContent: - '{"type":"quick_reply","content":{"type":"text","header":"Continue","text":"Do you want to continue?"},"options":[{"type":"text","title":"Yes"},{"type":"text","title":"No"}]}', - tag_id: '1', + '{"type":"quick_reply","content":{"type":"text","header":"Details Confirmation","text":"Please *confirm* if the below details are correct-\\n\\n*Name:* @results.name\\n*Profile of:* @results.role"},"options":[{"type":"text","title":"Correct"},{"type":"text","title":"Re-enter details"}]}', languageId: '1', - label: 'Continue', - sendWithTitle: false, + label: 'Details Confirmation', + sendWithTitle: true, translations: - '{"1":{"type":"quick_reply","content":{"type":"text","header":"Continue","text":"Do you want to continue?"},"options":[{"type":"text","title":"Yes"},{"type":"text","title":"No"}]}}', + '{"1":{"type":"quick_reply","content":{"type":"text","header":"Details Confirmation","text":"Please *confirm* if the below details are correct-\\n\\n*Name:* @results.name\\n*Profile of:* @results.role"},"options":[{"type":"text","title":"Correct"},{"type":"text","title":"Re-enter details"}]},"2":{"type":"quick_reply","options":[{"type":"text","title":"सही"},{"type":"text","title":"विवरण प"}],"content":{"type":"text","text":"कृपया पुष्टि करें कि नीचे दी गई जानकारी सही है या नहीं-\\n\\n*नाम:* @results.name\\n*प्रोफ़ाइल:* @results.role","header":"विवरण पुष्टि"}}}', +}; + +const quickReplyMockInput2 = { + type: 'QUICK_REPLY', + interactiveContent: + '{"type":"quick_reply","options":[{"type":"text","title":"Correct"},{"type":"text","title":"Re-enter details"}],"content":{"type":"text","text":"Please *confirm* if the below details are correct-\\n\\n*Name:* @results.name\\n*Profile of:* @results.role","header":"Details Confirmation"}}', + languageId: '1', + sendWithTitle: true, + translations: + '{"1":{"type":"quick_reply","content":{"type":"text","header":"Details Confirmation","text":"Please *confirm* if the below details are correct-\\n\\n*Name:* @results.name\\n*Profile of:* @results.role"},"options":[{"type":"text","title":"Correct"},{"type":"text","title":"Re-enter details"}]},"2":{"type":"quick_reply","content":{"type":"text","header":"विवरण पुष्टि","text":"कृपया पुष्टि करें कि नीचे दी गई जानकारी सही है या नहीं-\\n\\n*नाम:* @results.name\\n*प्रोफ़ाइल:* @results.role"},"options":[{"type":"text","title":"सही"},{"type":"text","title":"विवरण प"}]}}', }; const quickReplyMedia = { @@ -269,7 +277,7 @@ const createInteractiveCustomMock = () => ({ }, }); -const updateMockByType = (id: string, input: any, response: any) => ({ +const updateMockByType = (id: string, input: any, response: any, message: any = null) => ({ request: { query: UPDATE_INTERACTIVE, variables: { @@ -289,6 +297,7 @@ const updateMockByType = (id: string, input: any, response: any) => ({ ...response, }, errors: null, + message, }, errors: null, }, @@ -364,11 +373,31 @@ export const translateInteractiveTemplateMock = (error: boolean = false) => ({ translateInteractiveTemplate: { interactiveTemplate: { ...quickReplyResult, tag: null, id: '1' }, errors: null, + message: null, }, }, }, }); +const trimmingMessage = + 'Trimming has been done for the following languages due to exceeding character limits: Hindi. Please verify the content before saving.'; + +export const translateInteractiveTemplateWithTrimMock = { + request: { + query: TRANSLATE_INTERACTIVE_TEMPLATE, + variables: { translateInteractiveTemplateId: '1' }, + }, + result: { + data: { + translateInteractiveTemplate: { + interactiveTemplate: { ...quickReplyResult, tag: null, id: '1' }, + errors: null, + message: trimmingMessage, + }, + }, + }, +}; + export const importInteractiveTemplateMock = (error: boolean = false) => ({ request: { query: IMPORT_INTERACTIVE_TEMPLATE, @@ -380,12 +409,29 @@ export const importInteractiveTemplateMock = (error: boolean = false) => ({ importInteractiveTemplate: { interactiveTemplate: { ...quickReplyResult, tag: null, id: '1' }, errors: null, + message: null, }, }, }, variableMatcher: (variables: any) => true, }); +export const importInteractiveTemplateWithTrimmingMock = { + request: { + query: IMPORT_INTERACTIVE_TEMPLATE, + }, + result: { + data: { + importInteractiveTemplate: { + interactiveTemplate: { ...quickReplyResult, tag: null, id: '1' }, + errors: null, + message: trimmingMessage, + }, + }, + }, + variableMatcher: (variables: any) => true, +}; + export const exportInteractiveTemplateMock = (error: boolean = false) => ({ request: { query: EXPORT_INTERACTIVE_TEMPLATE, @@ -436,17 +482,28 @@ export const mocks: any = [ createMockByType(listReplyMock), createInteractiveCustomMock(), updateMockByType('1', quickReplyMockInput, quickReplyMock), + updateMockByType('1', quickReplyMockInput2, quickReplyMock), updateMockByType('2', listReplyMock, listReplyMock), updateMockByType('3', quickReply, quickReplyResult), getTemplateByType('1', quickReplyMock), getTemplateByType('2', listReplyMock), getTemplateByType('3', quickReplyMedia), + getTemplateByType('4', quickReplyMock), + updateMockByType('4', quickReplyMockInput, quickReplyMock, trimmingMessage), createMockByType(quick_reply), deleteMock, getFilterTagQuery, getOrganizationLanguagesWithoutOrder, +]; + +export const translateWithoutTrimmingMocks = [ translateInteractiveTemplateMock(), importInteractiveTemplateMock(), exportInteractiveTemplateMock(), exportInteractiveTemplateMockWithoutTranslation(), ]; + +export const translateWitTrimmingMocks = [ + translateInteractiveTemplateWithTrimMock, + importInteractiveTemplateWithTrimmingMock, +];