From 062a26fe1d7db645b299cad8cde3ad151258277c Mon Sep 17 00:00:00 2001 From: Santiago <71732018+Zasa-san@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:31:19 -0300 Subject: [PATCH] V2 translate (#6569) * translations atom * migrate Translate WIP * handle inline edit mode * stop event * adds update translations endpoint * wip modal form * get translations V2 WIP * Translate component using translationsAtom and loading correct values in to the trtanslation form * test new endpoint * correctly set form * context to post request * update translate atom after update * uses context by language * look & feel + socket events * ci fixes * migrate t and update cache reset strategy * update affected files * adjust zindex and use t function for texts inside * migrate I18Nmenu * remove icon dependency * cleanup * update import * fix issue with dropdown * update t function and move to v2 folder * update imports * remove unused afterEach * stories cleanup * remove redux provider from pdf unit test * remove commented * locale sensitve i18n link * remove legacy store * use new i18n link for breadcrumbs * reorganize files * unit test snapshot update * update import * avoid undefined when no classname and update snapshots * revert some unnecessary changes * do not return component in notification * fix type + wip fix test * fix export * fix multiple unit test by updating selectors, stores or mocking translate * more fixes * revert removal of redux translations * fix test and eslint errors * update snapshot * update selectors and e2e * more specific selector * wait for modal * attempt to stabilize * I18NMenu test update wip * use actual store for test * use real events * close modal after a11y check * more attempts at fixing e2e * more explicit steps * continued attempt at fixing test * install function handles modal closing * more specific install selector * restore workflow file * account for new languages in socket emit * wip update api * set translation modal's zindex to 10000 * propagation of thesaurus translations * socket events test update * update translations on language delete * udpate how the test updates component * translateModal test wip * test modal closes properly * update submit test * update response type * validate form and notify * fix lint error * stabilze e2e * disable modal elements while saving * update test description * update snapshot * more attempts at e2e stabiliation * cleanup * fix route duplication --------- Co-authored-by: mfacar Co-authored-by: A happy cat Co-authored-by: JoshuaD Co-authored-by: Joan Gallego Girona --- app/api/api.js | 1 + app/api/i18n.v2/routes/index.ts | 54 ++++ app/api/i18n.v2/routes/specs/routes.spec.ts | 102 +++++++ app/api/i18n/routes.ts | 2 + app/api/i18n/specs/fixtures.ts | 22 ++ app/api/i18n/specs/translations.spec.ts | 34 ++- app/api/i18n/translations.ts | 103 ++++--- app/api/i18n/v2_support.ts | 22 +- app/react/App/App.js | 6 +- app/react/App/DropdownMenu.tsx | 2 +- app/react/App/SearchTipsContent.tsx | 98 +++--- app/react/App/scss/layout/_header.scss | 10 +- app/react/App/sockets.js | 35 ++- .../specs/__snapshots__/Confirm.spec.js.snap | 32 +- .../__snapshots__/Cookiepopup.spec.js.snap | 4 +- app/react/App/specs/fixtures/fixtures.ts | 155 ++++++++++ app/react/App/specs/sockets.spec.js | 66 +++- .../Attachments/components/AttachmentForm.js | 2 +- .../components/UploadAttachment.js | 2 +- .../AttachmentsList.spec.js.snap | 20 +- .../AttachmentsModal.spec.tsx.snap | 22 +- .../WebMediaResourceForm.spec.tsx.snap | 8 +- .../components/specs/ConnectionsGroup.spec.js | 12 +- .../DocumentContentSnippets.spec.js.snap | 6 +- .../DocumentSidePanel.spec.js.snap | 4 +- .../MetadataFieldSnippets.spec.js.snap | 12 +- .../__snapshots__/SearchText.spec.js.snap | 24 +- .../Forms/components/specs/Switcher.spec.tsx | 4 +- .../__snapshots__/DateRange.spec.js.snap | 16 +- .../__snapshots__/MultiSelect.spec.js.snap | 14 +- app/react/I18N/I18NLinkV2.tsx | 24 ++ app/react/I18N/Translate.tsx | 82 +++++ app/react/I18N/TranslateModal.tsx | 131 ++++++++ app/react/I18N/actions/I18NActions.js | 2 +- app/react/I18N/components/I18N.js | 6 +- app/react/I18N/components/I18NMenu.tsx | 91 +++--- app/react/I18N/components/Translate.js | 111 ------- app/react/I18N/components/TranslateForm.js | 100 ------ .../I18N/components/specs/I18NMenu.spec.tsx | 289 ++++++++++-------- .../I18N/components/specs/Translate.spec.js | 144 --------- .../components/specs/TranslateForm.spec.js | 97 ------ .../__snapshots__/I18NMenu.spec.tsx.snap | 131 ++++++++ app/react/I18N/index.js | 21 -- app/react/I18N/index.ts | 13 + app/react/I18N/specs/I18NLinkV2.spec.tsx | 55 ++++ app/react/I18N/specs/TranslateModal.spec.tsx | 138 +++++++++ app/react/I18N/specs/fixtures.ts | 64 ++++ app/react/I18N/specs/t.spec.js | 77 ----- .../I18N/specs/translateFunction.spec.tsx | 113 +++++++ app/react/I18N/t.js | 35 --- app/react/I18N/translateFunction.tsx | 43 +++ app/react/Layout/DocumentLanguage.js | 2 +- app/react/Layout/ItemSnippet.js | 2 +- app/react/Layout/TemplateLabel.js | 2 +- .../Layout/specs/DocumentLanguage.spec.js | 5 + .../__snapshots__/BackButton.spec.js.snap | 6 +- .../__snapshots__/ConfirmModal.spec.js.snap | 16 +- .../__snapshots__/ItemSnippet.spec.js.snap | 60 +++- app/react/Library/actions/exportActions.ts | 6 +- .../specs/DocumentTypesList.spec.js | 6 +- .../specs/__snapshots__/Doc.spec.js.snap | 6 + .../__snapshots__/NestedFilter.spec.js.snap | 4 +- .../QuickLabelHeader.spec.tsx.snap | 24 +- .../QuickLabelPanel.spec.tsx.snap | 22 +- .../__snapshots__/ViewDocButton.spec.js.snap | 16 +- .../components/specs/EntityData.spec.tsx | 16 +- .../__snapshots__/ContactForm.spec.js.snap | 16 +- .../__snapshots__/MarkdownMedia.spec.tsx.snap | 4 +- .../components/specs/fixture/state.ts | 27 +- .../__snapshots__/MarkdownViewer.spec.js.snap | 4 +- .../GeolocationViewer.spec.js.snap | 16 +- .../__snapshots__/IconField.spec.js.snap | 32 +- .../specs/__snapshots__/Metadata.spec.js.snap | 108 +++++-- .../specs/LoadMoreRelationshipsButton.spec.js | 6 +- .../specs/RelationshipsFormButtons.spec.tsx | 6 +- .../HubRelationshipMetadata.spec.js.snap | 6 +- .../__snapshots__/OneUpTitleBar.spec.tsx.snap | 42 ++- .../DocumentResults.spec.js.snap | 28 +- .../ResultsFiltersPanel.spec.js.snap | 62 +++- .../SemanticSearchMultieditPanel.spec.js.snap | 40 ++- .../SemanticSearchResults.spec.js.snap | 44 +-- .../Templates/components/FormConfigSelect.tsx | 2 +- .../specs/FormConfigSelect.spec.tsx | 85 +++--- .../specs/TemplateAsPageControl.spec.tsx | 5 + .../FormConfigMultimedia.spec.js.snap | 172 +++++------ .../FormConfigRelationship.spec.js.snap | 36 +-- .../MetadataProperty.spec.js.snap | 8 +- .../PropertyConfigOption.spec.js.snap | 8 +- .../PropertyConfigOptions.spec.js.snap | 212 ++++++------- .../TemplateCreator.spec.js.snap | 28 +- .../utils/specs/getFieldLabel.spec.js | 4 +- .../__snapshots__/ImportPanel.spec.js.snap | 28 +- .../__snapshots__/ImportProgress.spec.js.snap | 20 +- .../specs/__snapshots__/Login.spec.js.snap | 78 +++-- app/react/V2/Components/Forms/Geolocation.tsx | 2 +- .../Forms/specs/MultiselectList.cy.tsx | 96 +++--- .../V2/Components/Layouts/SettingsContent.tsx | 8 +- .../Layouts/specs/SettingsContent.cy.tsx | 48 ++- .../Components/PDFViewer/specs/PDF.spec.tsx | 11 +- app/react/V2/Components/UI/Modal.tsx | 4 +- .../Components/UI/specs/CopyValueInput.cy.tsx | 13 +- .../UI/specs/NotificationsContainer.cy.tsx | 16 +- .../components/FiltersSidePanel.tsx | 4 +- .../components/InstallLanguagesModal.tsx | 2 +- .../ParagraphExtraction/PXParagraphs.tsx | 15 +- .../Thesauri/components/TableComponents.tsx | 2 +- .../components/ThesauriGroupFormSidepanel.tsx | 4 +- .../Thesauri/components/ThesauriTable.tsx | 2 +- .../components/ThesauriValueFormSidepanel.tsx | 4 +- .../Settings/Thesauri/specs/Thesauri.spec.tsx | 23 +- .../__snapshots__/Thesauri.spec.tsx.snap | 66 ++-- app/react/V2/api/translations/index.ts | 41 ++- app/react/V2/atoms/index.ts | 2 +- app/react/V2/atoms/store.ts | 12 +- .../{templatesAtom.tsx => templatesAtom.ts} | 0 app/react/V2/atoms/translationsAtom.tsx | 5 - app/react/V2/atoms/translationsAtoms.ts | 8 + app/react/V2/shared/types.ts | 16 +- .../ConnectionsList.spec.js.snap | 24 +- .../__snapshots__/Paginator.spec.js.snap | 176 ++++------- app/react/entry-server.tsx | 12 +- .../stories/Buttons/EmbededButton.stories.tsx | 26 +- app/react/stories/CodeEditor.stories.tsx | 46 ++- .../stories/ConfirmationModal.stories.tsx | 34 +-- app/react/stories/ErrorBoundary.stories.tsx | 10 +- app/react/stories/Forms/Checkbox.stories.tsx | 22 +- .../stories/Forms/ColorPicker.stories.tsx | 20 +- .../stories/Forms/DatePicker.stories.tsx | 39 ++- .../stories/Forms/DateRangePicker.stories.tsx | 32 +- .../Forms/EnableButtonCheckbox.stories.tsx | 16 +- .../stories/Forms/FileDropzone.stories.tsx | 10 +- .../stories/Forms/InputField.stories.tsx | 12 +- .../stories/Forms/MultiSelect.stories.tsx | 32 +- .../stories/Forms/MultiselectList.stories.tsx | 50 ++- .../stories/Forms/RadioSelect.stories.tsx | 22 +- app/react/stories/MediaPlayer.stories.tsx | 22 +- app/react/stories/Notification.stories.tsx | 22 +- app/react/stories/PDF.stories.tsx | 30 +- app/react/stories/Paginator.stories.tsx | 22 +- app/react/stories/Sidepanel.stories.tsx | 113 ++++--- app/react/stories/Table.stories.tsx | 22 +- .../utils/useOnClickOutsideElementHook.ts | 2 +- app/shared/translate.js | 8 +- ...epanel and show in the correct page #0.png | Bin 104058 -> 0 bytes ...sidepanel and show the correct page #0.png | Bin 0 -> 103827 bytes cypress/e2e/pdf-display.cy.ts | 35 ++- .../__snapshots__/activitylog.cy.ts.snap | 88 +----- cypress/e2e/settings/languages.cy.ts | 45 ++- cypress/e2e/settings/translations.cy.ts | 16 +- tailwind.config.js | 2 +- 150 files changed, 3114 insertions(+), 2281 deletions(-) create mode 100644 app/api/i18n.v2/routes/index.ts create mode 100644 app/api/i18n.v2/routes/specs/routes.spec.ts create mode 100644 app/react/App/specs/fixtures/fixtures.ts create mode 100644 app/react/I18N/I18NLinkV2.tsx create mode 100644 app/react/I18N/Translate.tsx create mode 100644 app/react/I18N/TranslateModal.tsx delete mode 100644 app/react/I18N/components/Translate.js delete mode 100644 app/react/I18N/components/TranslateForm.js delete mode 100644 app/react/I18N/components/specs/Translate.spec.js delete mode 100644 app/react/I18N/components/specs/TranslateForm.spec.js create mode 100644 app/react/I18N/components/specs/__snapshots__/I18NMenu.spec.tsx.snap delete mode 100644 app/react/I18N/index.js create mode 100644 app/react/I18N/index.ts create mode 100644 app/react/I18N/specs/I18NLinkV2.spec.tsx create mode 100644 app/react/I18N/specs/TranslateModal.spec.tsx create mode 100644 app/react/I18N/specs/fixtures.ts delete mode 100644 app/react/I18N/specs/t.spec.js create mode 100644 app/react/I18N/specs/translateFunction.spec.tsx delete mode 100644 app/react/I18N/t.js create mode 100644 app/react/I18N/translateFunction.tsx rename app/react/V2/atoms/{templatesAtom.tsx => templatesAtom.ts} (100%) delete mode 100644 app/react/V2/atoms/translationsAtom.tsx create mode 100644 app/react/V2/atoms/translationsAtoms.ts delete mode 100644 cypress/e2e/__image_snapshots__/PDF display responsiveness IX sidepanel should open the pdf sidepanel and show in the correct page #0.png create mode 100644 cypress/e2e/__image_snapshots__/PDF display responsiveness IX sidepanel should open the pdf sidepanel and show the correct page #0.png diff --git a/app/api/api.js b/app/api/api.js index 3552e468a7..9857a26925 100644 --- a/app/api/api.js +++ b/app/api/api.js @@ -33,6 +33,7 @@ export default (app, server) => { require('./files/ocrRoutes').ocrRoutes(app); require('./settings/routes').default(app); require('./i18n/routes').default(app); + require('./i18n.v2/routes').translationsRoutes(app); require('./sync/routes').default(app); require('./tasks/routes').default(app); require('./usergroups/routes').default(app); diff --git a/app/api/i18n.v2/routes/index.ts b/app/api/i18n.v2/routes/index.ts new file mode 100644 index 0000000000..1e4a94a547 --- /dev/null +++ b/app/api/i18n.v2/routes/index.ts @@ -0,0 +1,54 @@ +import { Application, Request } from 'express'; +import { needsAuthorization } from 'api/auth'; +import { validation } from 'api/utils'; +import translations from 'api/i18n'; +import { getTranslationsEntriesV2 } from 'api/i18n/v2_support'; + +const translationsRoutes = (app: Application) => { + app.get('/api/v2/translations', async (_req: Request, res) => { + const translationsV2 = await getTranslationsEntriesV2(); + const translationList = await translationsV2.all(); + res.json(translationList); + }); + + app.post( + '/api/v2/translations', + needsAuthorization(), + validation.validateRequest({ + type: 'object', + properties: { + body: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { type: 'string' }, + language: { type: 'string' }, + key: { type: 'string' }, + value: { type: 'string' }, + context: { + type: 'object', + properties: { + id: { type: 'string' }, + label: { type: 'string' }, + type: { type: 'string' }, + }, + required: ['id', 'label', 'type'], + }, + }, + required: ['language', 'key', 'value', 'context'], + }, + }, + }, + required: ['body'], + }), + async (req, res) => { + await translations.v2StructureSave(req.body); + req.sockets.emitToCurrentTenant('translationKeysChange', req.body); + res.status(200); + res.json({ success: true }); + } + ); +}; + +export { translationsRoutes }; diff --git a/app/api/i18n.v2/routes/specs/routes.spec.ts b/app/api/i18n.v2/routes/specs/routes.spec.ts new file mode 100644 index 0000000000..dfdc40de32 --- /dev/null +++ b/app/api/i18n.v2/routes/specs/routes.spec.ts @@ -0,0 +1,102 @@ +import 'isomorphic-fetch'; +import request from 'supertest'; + +import { TranslationDBO } from 'api/i18n.v2/schemas/TranslationDBO'; +import { getFixturesFactory } from 'api/utils/fixturesFactory'; +import { testingEnvironment } from 'api/utils/testingEnvironment'; +import { TestEmitSources, iosocket, setUpApp } from 'api/utils/testingRoutes'; +import { UserRole } from 'shared/types/userSchema'; +import { translationsRoutes } from '..'; + +describe('i18n translations V2 routes', () => { + const createTranslationDBO = getFixturesFactory().v2.database.translationDBO; + const app = setUpApp(translationsRoutes, (req, _res, next) => { + req.user = { + username: 'admin', + role: UserRole.ADMIN, + email: 'admin@test.com', + }; + // @ts-ignore + req.file = { path: 'filder/filename.ext' }; + next(); + }); + + beforeEach(async () => { + const translationsV2: TranslationDBO[] = [ + createTranslationDBO('Search', 'Buscar', 'es', { + id: 'System', + type: 'Entity', + label: 'User Interface', + }), + createTranslationDBO('Search', 'Search', 'en', { + id: 'System', + type: 'Uwazi UI', + label: 'User Interface', + }), + ]; + await testingEnvironment.setUp( + { + settings: [ + { + languages: [ + { key: 'en', label: 'English', default: true }, + { key: 'es', label: 'Spanish', default: false }, + ], + }, + ], + translationsV2, + }, + 'index_i18n_v2_routes' + ); + }); + + afterEach(() => { + iosocket.emit.mockReset(); + }); + + afterAll(async () => { + await testingEnvironment.tearDown(); + }); + + describe('/api/v2/translations', () => { + it('should update the translations and emit translationKeysChange event', async () => { + const response = await request(app) + .post('/api/v2/translations') + .send([ + { + language: 'es', + key: 'Search', + value: 'Búsqueda', + context: { + id: 'System', + label: 'User Interface', + type: 'Uwazi UI', + }, + }, + ]); + expect(response.status).toEqual(200); + expect(iosocket.emit).toHaveBeenCalledWith( + 'translationKeysChange', + TestEmitSources.currentTenant, + [ + { + context: { id: 'System', label: 'User Interface', type: 'Uwazi UI' }, + key: 'Search', + language: 'es', + value: 'Búsqueda', + }, + ] + ); + }); + + it('should handle invalid POST request payload', async () => { + const response = await request(app) + .post('/api/v2/translations') + .send({ invalidKey: 'value' }); // Invalid payload + expect(response.status).toBe(400); + expect(response.body).toEqual( + expect.objectContaining({ prettyMessage: 'validation failed' }) + ); + }); + }); +}); diff --git a/app/api/i18n/routes.ts b/app/api/i18n/routes.ts index 7382258053..b4764f2357 100644 --- a/app/api/i18n/routes.ts +++ b/app/api/i18n/routes.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ import { createError, validation } from 'api/utils'; import settings from 'api/settings'; import entities from 'api/entities'; @@ -59,6 +60,7 @@ async function deleteLanguage(key: LanguageISO6391, req: Request) { type TranslationsRequest = Request & { query: { context: string } }; +// eslint-disable-next-line max-statements export default (app: Application) => { app.get( '/api/translations', diff --git a/app/api/i18n/specs/fixtures.ts b/app/api/i18n/specs/fixtures.ts index bb0093846c..8b68dee4d4 100644 --- a/app/api/i18n/specs/fixtures.ts +++ b/app/api/i18n/specs/fixtures.ts @@ -199,6 +199,13 @@ const fixtures: DBFixture = { { _id: entityTemplateId, type: 'template', + properties: [ + { + type: 'select', + name: 'Dictionary', + content: dictionaryId.toString(), + }, + ], }, { _id: documentTemplateId, @@ -214,6 +221,21 @@ const fixtures: DBFixture = { published: false, metadata: {}, }, + { + language: 'es', + sharedId: 'entity1', + title: '1', + template: entityTemplateId, + published: false, + metadata: { + Dictionary: [ + { + value: '1', + label: 'Password', + }, + ], + }, + }, ], pages: [ { diff --git a/app/api/i18n/specs/translations.spec.ts b/app/api/i18n/specs/translations.spec.ts index 473c3a8beb..44ca0c6864 100644 --- a/app/api/i18n/specs/translations.spec.ts +++ b/app/api/i18n/specs/translations.spec.ts @@ -7,11 +7,13 @@ import thesauri from 'api/thesauri/thesauri.js'; import { ContextType } from 'shared/translationSchema'; // eslint-disable-next-line node/no-restricted-import import * as fs from 'fs'; +import { TranslationSyO } from 'api/i18n.v2/schemas/TranslationSyO'; import { UITranslationNotAvailable } from '../defaultTranslations'; import translations from '../translations'; import fixtures, { dictionaryId } from './fixtures'; import { sortByLocale } from './sortByLocale'; import { addLanguage } from '../routes'; +import { getTranslationsV2ByContext } from '../v2_support'; describe('translations', () => { beforeEach(async () => { @@ -29,7 +31,7 @@ describe('translations', () => { expect(result).toMatchObject({ contexts: [ { - type: 'Thesaurus', + type: 'Thesaurus' as 'Thesaurus', values: { Account: 'Account', Age: 'Age', @@ -56,6 +58,36 @@ describe('translations', () => { }); }); + describe('v2StructureSave', () => { + it('should save changed translations and propagate the changes', async () => { + const initialTranslations = await getTranslationsV2ByContext(dictionaryId.toString()); + const initialEntity = (await entities.get({ language: 'es', sharedId: 'entity1' }))[0]; + const translationsToSave = [ + { + _id: '1', + language: initialTranslations[0].locale!, + key: 'Password', + value: 'Changed Password ES', + context: { + id: dictionaryId.toString(), + type: 'Thesaurus' as TranslationSyO['context']['type'], + label: '', + }, + }, + ]; + + await translations.v2StructureSave(translationsToSave); + const updatedTranslations = await getTranslationsV2ByContext(dictionaryId.toString()); + initialTranslations![0]!.contexts![0]!.values!.find(v => v.key === 'Password')!.value = + 'Changed Password ES'; + expect(updatedTranslations).toEqual(initialTranslations); + + const updatedEntity = (await entities.get({ language: 'es', sharedId: 'entity1' }))[0]; + initialEntity.metadata.Dictionary[0].label = 'Changed Password ES'; + expect(updatedEntity).toEqual(initialEntity); + }); + }); + describe('save()', () => { it('should save the translation and return it', async () => { const result = await translations.save({ locale: 'fr' }); diff --git a/app/api/i18n/translations.ts b/app/api/i18n/translations.ts index d77c894553..2d83db209a 100644 --- a/app/api/i18n/translations.ts +++ b/app/api/i18n/translations.ts @@ -17,6 +17,7 @@ import { availableLanguages } from 'shared/language'; import { ContextType } from 'shared/translationSchema'; import { LanguageISO6391 } from 'shared/types/commonTypes'; import { pipeline } from 'stream/promises'; +import { TranslationSyO } from 'api/i18n.v2/schemas/TranslationSyO'; import { addLanguageV2, deleteTranslationsByContextIdV2, @@ -25,6 +26,7 @@ import { getTranslationsV2ByContext, getTranslationsV2ByLanguage, updateContextV2, + upsertTranslationEntries, upsertTranslationsV2, } from './v2_support'; @@ -105,6 +107,47 @@ function processContextValues(context: TranslationContext | IndexedContext): Tra return { ...context, values }; } +const propagateTranslationInMetadata = async ( + translation: TranslationType, + context: TranslationContext +) => { + const isPresentInTheComingData = (translation.contexts || []).find( + _context => _context.id?.toString() === context.id?.toString() + ); + + if (isPresentInTheComingData && isPresentInTheComingData.type === 'Thesaurus') { + const thesaurus = await thesauri.getById(context.id); + + const valuesChanged: IndexedContextValues = (isPresentInTheComingData.values || []).reduce( + (changes, value) => { + const currentValue = (context.values || []).find(v => v.key === value.key); + if (currentValue?.key && currentValue.value !== value.value) { + return { ...changes, [currentValue.key]: value.value } as IndexedContextValues; + } + return changes; + }, + {} as IndexedContextValues + ); + + const changesMatchingDictionaryId = Object.keys(valuesChanged) + .map(valueChanged => { + const valueFound = (thesaurus?.values || []).find(v => v.label === valueChanged); + if (valueFound?.id) { + return { id: valueFound.id, value: valuesChanged[valueChanged] }; + } + return null; + }) + .filter(a => a) as { id: string; value: string }[]; + + return Promise.all( + changesMatchingDictionaryId.map(async change => + thesauri.renameThesaurusInMetadata(change.id, change.value, context.id, translation.locale) + ) + ); + } + return Promise.resolve([]); +}; + const propagateTranslation = async ( translation: TranslationType, currentTranslationData: WithId @@ -112,47 +155,7 @@ const propagateTranslation = async ( await (currentTranslationData.contexts || ([] as TranslationContext[])).reduce( async (promise: Promise, context) => { await promise; - - const isPresentInTheComingData = (translation.contexts || []).find( - _context => _context.id?.toString() === context.id?.toString() - ); - - if (isPresentInTheComingData && isPresentInTheComingData.type === 'Thesaurus') { - const thesaurus = await thesauri.getById(context.id); - - const valuesChanged: IndexedContextValues = (isPresentInTheComingData.values || []).reduce( - (changes, value) => { - const currentValue = (context.values || []).find(v => v.key === value.key); - if (currentValue?.key && currentValue.value !== value.value) { - return { ...changes, [currentValue.key]: value.value } as IndexedContextValues; - } - return changes; - }, - {} as IndexedContextValues - ); - - const changesMatchingDictionaryId = Object.keys(valuesChanged) - .map(valueChanged => { - const valueFound = (thesaurus?.values || []).find(v => v.label === valueChanged); - if (valueFound?.id) { - return { id: valueFound.id, value: valuesChanged[valueChanged] }; - } - return null; - }) - .filter(a => a) as { id: string; value: string }[]; - - return Promise.all( - changesMatchingDictionaryId.map(async change => - thesauri.renameThesaurusInMetadata( - change.id, - change.value, - context.id, - translation.locale - ) - ) - ); - } - return Promise.resolve([]); + return propagateTranslationInMetadata(translation, context); }, Promise.resolve([]) ); @@ -209,6 +212,24 @@ export default { return translationToSave; }, + async v2StructureSave(translationsToSave: TranslationSyO[]) { + const { context } = translationsToSave[0]; + const currentTranslations = await getTranslationsV2ByContext(context.id); + await upsertTranslationEntries(translationsToSave); + const thesaurusTranslations = currentTranslations[0].contexts?.[0].type === 'Thesaurus'; + if (thesaurusTranslations) { + const updatedTranslations = await getTranslationsV2ByContext(context.id); + await Promise.all( + updatedTranslations.map(async translation => { + const originalContexts = currentTranslations.find( + t => t.locale === translation.locale + )?.contexts; + return propagateTranslationInMetadata(translation, (originalContexts || [context])[0]); + }) + ); + } + }, + async updateEntries( contextId: string, keyValuePairsPerLanguage: { diff --git a/app/api/i18n/v2_support.ts b/app/api/i18n/v2_support.ts index a2c4161d2b..5c51c2fbf5 100644 --- a/app/api/i18n/v2_support.ts +++ b/app/api/i18n/v2_support.ts @@ -110,7 +110,7 @@ export const createTranslationsV2 = async (translation: TranslationType) => { ).create(flattenTranslations(translation)); }; -export const upsertTranslationsV2 = async (translations: TranslationType[]) => { +export const upsertTranslationEntries = async (translations: CreateTranslationsData[]) => { const transactionManager = DefaultTransactionManager(); await new UpsertTranslationsService( DefaultTranslationsDataSource(transactionManager), @@ -120,12 +120,15 @@ export const upsertTranslationsV2 = async (translations: TranslationType[]) => { DefaultSettingsDataSource(transactionManager) ), transactionManager - ).upsert( - translations.reduce( - (flattened, t) => flattened.concat(flattenTranslations(t)), - [] - ) + ).upsert(translations); +}; + +export const upsertTranslationsV2 = async (translations: TranslationType[]) => { + const translationsToUpsert = translations.reduce( + (flattened, t) => flattened.concat(flattenTranslations(t)), + [] ); + return upsertTranslationEntries(translationsToUpsert); }; export const deleteTranslationsByContextIdV2 = async (contextId: string) => { @@ -159,10 +162,11 @@ export const getTranslationsV2ByLanguage = async (language: LanguageISO6391) => language ); +export const getTranslationsEntriesV2 = async () => + new GetTranslationsService(DefaultTranslationsDataSource(DefaultTransactionManager())).getAll(); + export const getTranslationsV2 = async () => - resultsToV1TranslationType( - new GetTranslationsService(DefaultTranslationsDataSource(DefaultTransactionManager())).getAll() - ); + resultsToV1TranslationType(await getTranslationsEntriesV2()); export const updateContextV2 = async ( context: CreateTranslationsData['context'], diff --git a/app/react/App/App.js b/app/react/App/App.js index 05d22199c6..12979af0db 100644 --- a/app/react/App/App.js +++ b/app/react/App/App.js @@ -5,12 +5,13 @@ import { Outlet, useLocation, useParams } from 'react-router-dom'; import { useAtom } from 'jotai'; import Notifications from 'app/Notifications'; import Cookiepopup from 'app/App/Cookiepopup'; -import { TranslateForm, t } from 'app/I18N'; import { Icon } from 'UI'; import { socket } from 'app/socket'; import { NotificationsContainer } from 'V2/Components/UI'; import { Matomo, CleanInsights } from 'app/V2/Components/Analitycs'; import { settingsAtom } from 'V2/atoms/settingsAtom'; +import { TranslateModal, t } from 'app/I18N'; +import { inlineEditAtom } from 'V2/atoms'; import Confirm from './Confirm'; import { Menu } from './Menu'; import { AppMainContext } from './AppMainContext'; @@ -26,6 +27,7 @@ import 'flowbite'; const App = ({ customParams }) => { const [showMenu, setShowMenu] = useState(false); + const [inlineEditState] = useAtom(inlineEditAtom); const [confirmOptions, setConfirmOptions] = useState({}); const [settings, setSettings] = useAtom(settingsAtom); @@ -92,7 +94,6 @@ const App = ({ customParams }) => {
- @@ -101,6 +102,7 @@ const App = ({ customParams }) => {
+ {inlineEditState.inlineEdit && inlineEditState.context && } ); }; diff --git a/app/react/App/DropdownMenu.tsx b/app/react/App/DropdownMenu.tsx index 1a0fb4f7bb..7b71a7703d 100644 --- a/app/react/App/DropdownMenu.tsx +++ b/app/react/App/DropdownMenu.tsx @@ -1,7 +1,7 @@ +import { useOnClickOutsideElement } from 'app/utils/useOnClickOutsideElementHook'; import { I18NLink, Translate } from 'app/I18N'; import { Icon } from 'UI'; import React, { useRef, useState, useCallback } from 'react'; -import { useOnClickOutsideElement } from 'app/utils/useOnClickOutsideElementHook'; import { ILink, ISublink } from 'app/V2/shared/types'; import { IImmutable } from 'shared/types/Immutable'; diff --git a/app/react/App/SearchTipsContent.tsx b/app/react/App/SearchTipsContent.tsx index 3e9ec3e3a8..5b6ee330bd 100644 --- a/app/react/App/SearchTipsContent.tsx +++ b/app/react/App/SearchTipsContent.tsx @@ -1,55 +1,53 @@ import React from 'react'; import { t } from 'app/I18N'; -const SearchTipsContent = () => { - return ( -
    -
  • - {t( - 'System', - 'Search Tips: wildcard', - 'Use an * for wildcard search. Ie: "juris*" will match words ' + - 'such as jurisdiction, jurisdictional, jurists, jurisprudence, etc.', - false - )} -
  • -
  • - {t( - 'System', - 'Search Tips: one char wildcard', - '? for one character wildcard. Ie: "198?" will match 1980 to 1989 and also 198a, 198b, etc.', - false - )} -
  • -
  • - {t( - 'System', - 'Search Tips: exact term', - 'Exact term match by enclosing your search string with quotes. Ie. "Costa Rica"' + - ' will toss different results compared to Costa Rica without quotes.', - false - )} -
  • -
  • - {t( - 'System', - 'Search Tips: proximity', - '~ for proximity searches. Ie: "the status"~5 will find anything having "the" and' + - '"status" within a distance of 5 words, such as "the procedural status", "the specific legal status".', - false - )} -
  • -
  • - {t( - 'System', - 'Search Tips: boolean', - 'AND, OR and NOT for boolean searches. Ie. "status AND women NOT Nicaragua" will match anything ' + - 'containing both the words status and women, and necessarily not containing the word Nicaragua.', - false - )} -
  • -
- ); -}; +const SearchTipsContent = () => ( +
    +
  • + {t( + 'System', + 'Search Tips: wildcard', + 'Use an * for wildcard search. Ie: "juris*" will match words ' + + 'such as jurisdiction, jurisdictional, jurists, jurisprudence, etc.', + false + )} +
  • +
  • + {t( + 'System', + 'Search Tips: one char wildcard', + '? for one character wildcard. Ie: "198?" will match 1980 to 1989 and also 198a, 198b, etc.', + false + )} +
  • +
  • + {t( + 'System', + 'Search Tips: exact term', + 'Exact term match by enclosing your search string with quotes. Ie. "Costa Rica"' + + ' will toss different results compared to Costa Rica without quotes.', + false + )} +
  • +
  • + {t( + 'System', + 'Search Tips: proximity', + '~ for proximity searches. Ie: "the status"~5 will find anything having "the" and' + + '"status" within a distance of 5 words, such as "the procedural status", "the specific legal status".', + false + )} +
  • +
  • + {t( + 'System', + 'Search Tips: boolean', + 'AND, OR and NOT for boolean searches. Ie. "status AND women NOT Nicaragua" will match anything ' + + 'containing both the words status and women, and necessarily not containing the word Nicaragua.', + false + )} +
  • +
+); export { SearchTipsContent }; diff --git a/app/react/App/scss/layout/_header.scss b/app/react/App/scss/layout/_header.scss index ca0943a5e3..892e0c559c 100644 --- a/app/react/App/scss/layout/_header.scss +++ b/app/react/App/scss/layout/_header.scss @@ -1,4 +1,4 @@ -@use "sass:color"; +@use 'sass:color'; @import '../config/colors'; @import '../elements/tooltip'; @@ -254,14 +254,6 @@ header { } } - .live-on { - color: #88eacd; - } - - .live-off { - color: #ffe66b; - } - button.singleItem { padding-left: 1em; } diff --git a/app/react/App/sockets.js b/app/react/App/sockets.js index c1785eb0c2..af401ebbe7 100644 --- a/app/react/App/sockets.js +++ b/app/react/App/sockets.js @@ -1,7 +1,8 @@ import { actions } from 'app/BasicReducer'; -import { t, Translate } from 'app/I18N'; +import { t } from 'app/I18N'; import { notificationActions } from 'app/Notifications'; import { documentProcessed } from 'app/Uploads/actions/uploadsActions'; +import { atomStore, translationsAtom } from 'V2/atoms'; import { store } from '../store'; import { socket, reconnectSocket } from '../socket'; @@ -58,10 +59,28 @@ socket.on('thesauriDelete', thesauri => { store.dispatch(actions.remove('thesauris', { _id: thesauri.id })); }); -socket.on('translationsChange', translations => { - store.dispatch(actions.update('translations', translations, 'locale')); - t.resetCachedTranslation(); - Translate.resetCachedTranslation(); +socket.on('translationsChange', languageTranslations => { + const translations = atomStore.get(translationsAtom); + const modifiedLanguage = translations.find( + translation => translation.locale === languageTranslations.locale + ); + if (modifiedLanguage) { + modifiedLanguage.contexts = languageTranslations.contexts; + } else { + translations.push(languageTranslations); + } + atomStore.set(translationsAtom, translations); +}); + +socket.on('translationKeysChange', translationsEntries => { + const translations = atomStore.get(translationsAtom); + translationsEntries.forEach(item => { + const modifiedContext = translations + .find(translation => translation.locale === item.language) + .contexts.find(c => c.id && c.id === item.context.id); + modifiedContext.values[item.key] = item.value; + }); + atomStore.set(translationsAtom, translations); }); socket.on('translationsInstallDone', () => { @@ -88,9 +107,9 @@ socket.on('translationsInstallError', errorMessage => { }); socket.on('translationsDelete', locale => { - store.dispatch(actions.remove('translations', { locale }, 'locale')); - t.resetCachedTranslation(); - Translate.resetCachedTranslation(); + const translations = atomStore.get(translationsAtom); + const updatedTranslations = translations.filter(language => language.locale !== locale); + atomStore.set(translationsAtom, updatedTranslations); }); socket.on('translationsDeleteDone', () => { diff --git a/app/react/App/specs/__snapshots__/Confirm.spec.js.snap b/app/react/App/specs/__snapshots__/Confirm.spec.js.snap index c850ab82e4..8985807101 100644 --- a/app/react/App/specs/__snapshots__/Confirm.spec.js.snap +++ b/app/react/App/specs/__snapshots__/Confirm.spec.js.snap @@ -9,21 +9,21 @@ exports[`CantDeleteTemplateAlert extraConfirm option should render a confirm inp >

- + Confirm action - +

- Are you sure you want to continue? - +

- + If you want to continue, please type - + ' CONFIRM ' @@ -41,9 +41,9 @@ exports[`CantDeleteTemplateAlert extraConfirm option should render a confirm inp onClick={[Function]} type="button" > - + Cancel - + @@ -68,16 +68,16 @@ exports[`CantDeleteTemplateAlert noCancel option should hide the cancel button 1 >

- + Confirm action - +

- Are you sure you want to continue? - +

@@ -87,9 +87,9 @@ exports[`CantDeleteTemplateAlert noCancel option should hide the cancel button 1 onClick={[Function]} type="button" > - + Accept - +
diff --git a/app/react/App/specs/__snapshots__/Cookiepopup.spec.js.snap b/app/react/App/specs/__snapshots__/Cookiepopup.spec.js.snap index 867950e1ec..29a3c72095 100644 --- a/app/react/App/specs/__snapshots__/Cookiepopup.spec.js.snap +++ b/app/react/App/specs/__snapshots__/Cookiepopup.spec.js.snap @@ -13,9 +13,9 @@ exports[`Cookiepopup when the cookiepolicy is active and the cookie not exists s + To bring you a better experience, this site uses cookies. - + } removeNotification={[Function]} type="success" diff --git a/app/react/App/specs/fixtures/fixtures.ts b/app/react/App/specs/fixtures/fixtures.ts new file mode 100644 index 0000000000..3205bc1a23 --- /dev/null +++ b/app/react/App/specs/fixtures/fixtures.ts @@ -0,0 +1,155 @@ +import { ClientTranslationSchema } from 'app/istore'; + +const currentTranslations: ClientTranslationSchema[] = [ + { + locale: 'en', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Document', + Select: 'Select', + Title: 'Title', + }, + }, + ], + }, + { + locale: 'es', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Documento', + Select: 'Selector', + Title: 'Título', + }, + }, + ], + }, +]; + +const updatedTranslation: ClientTranslationSchema = { + locale: 'en', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Document', + Select: 'Select', + Title: 'Update title', + }, + }, + ], +}; + +const newLanguage: ClientTranslationSchema = { + locale: 'fr', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Document', + Select: 'Select', + Title: 'Title', + }, + }, + ], +}; + +const translationKeysChangeArguments = [ + { + language: 'en', + value: 'Select', + key: 'Select', + context: { + id: 'id1', + label: 'Documents', + type: 'Entity', + }, + }, + { + language: 'es', + value: 'Select ES', + key: 'Select', + context: { + id: 'id1', + label: 'Documents', + type: 'Entity', + }, + }, + { + language: 'fr', + value: 'Select FR', + key: 'Select', + context: { + id: 'id1', + label: 'Documents', + type: 'Entity', + }, + }, +]; + +const translationKeysChangeResult = [ + { + locale: 'en', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Document', + Select: 'Select', + Title: 'Title', + }, + }, + ], + }, + { + locale: 'es', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Documento', + Select: 'Select ES', + Title: 'Título', + }, + }, + ], + }, + { + locale: 'fr', + contexts: [ + { + id: 'id1', + label: 'Documents', + type: 'Entity', + values: { + Documents: 'Document', + Select: 'Select FR', + Title: 'Title', + }, + }, + ], + }, +]; + +export { + updatedTranslation, + currentTranslations, + newLanguage, + translationKeysChangeResult, + translationKeysChangeArguments, +}; diff --git a/app/react/App/specs/sockets.spec.js b/app/react/App/specs/sockets.spec.js index a68978f88d..54e36e36a0 100644 --- a/app/react/App/specs/sockets.spec.js +++ b/app/react/App/specs/sockets.spec.js @@ -1,10 +1,19 @@ /** * @jest-environment jsdom */ +/* eslint-disable max-statements */ import * as uploadActions from 'app/Uploads/actions/uploadsActions'; +import { atomStore, translationsAtom } from 'V2/atoms'; import { socket } from '../../socket'; import '../sockets'; import { store } from '../../store'; +import { + currentTranslations, + newLanguage, + updatedTranslation, + translationKeysChangeArguments, + translationKeysChangeResult, +} from './fixtures/fixtures'; describe('sockets', () => { beforeEach(() => { @@ -113,13 +122,42 @@ describe('sockets', () => { }); describe('translationsChange', () => { + beforeEach(() => { + atomStore.set( + translationsAtom, + currentTranslations.map(t => ({ ...t })) + ); + spyOn(atomStore, 'set'); + }); + it('should emit a translationsChange event', () => { - socket._callbacks.$translationsChange[0]({ id: '123' }); - expect(store.dispatch).toHaveBeenCalledWith({ - customIndex: 'locale', - type: 'translations/UPDATE', - value: { id: '123' }, - }); + socket._callbacks.$translationsChange[0](updatedTranslation); + expect(atomStore.set).toHaveBeenCalledWith( + expect.any(Object), + expect.arrayContaining([updatedTranslation, currentTranslations[1]]) + ); + }); + + it('should add a new language to the translations', () => { + socket._callbacks.$translationsChange[0](newLanguage); + expect(atomStore.set).toHaveBeenCalledWith( + expect.any(Object), + expect.arrayContaining([...currentTranslations, newLanguage]) + ); + }); + }); + + describe('translationKeysChange', () => { + const initialTranslations = [...currentTranslations.map(t => ({ ...t })), newLanguage]; + + beforeEach(() => { + atomStore.set(translationsAtom, initialTranslations); + spyOn(atomStore, 'set'); + }); + + it('should emit a translationKeysChange event', () => { + socket._callbacks.$translationKeysChange[0](translationKeysChangeArguments); + expect(atomStore.set).toHaveBeenCalledWith(expect.any(Object), translationKeysChangeResult); }); }); @@ -152,13 +190,17 @@ describe('sockets', () => { }); describe('translationsDelete', () => { + beforeEach(() => { + atomStore.set( + translationsAtom, + currentTranslations.map(t => ({ ...t })) + ); + spyOn(atomStore, 'set'); + }); + it('should emit a translationsDelete event', () => { - socket._callbacks.$translationsDelete[0]('localeString'); - expect(store.dispatch).toHaveBeenCalledWith({ - customIndex: 'locale', - type: 'translations/REMOVE', - value: { locale: 'localeString' }, - }); + socket._callbacks.$translationsDelete[0]('es'); + expect(atomStore.set).toHaveBeenCalledWith(expect.any(Object), [currentTranslations[0]]); }); }); diff --git a/app/react/Attachments/components/AttachmentForm.js b/app/react/Attachments/components/AttachmentForm.js index 9b38b393cd..caeff3d6eb 100644 --- a/app/react/Attachments/components/AttachmentForm.js +++ b/app/react/Attachments/components/AttachmentForm.js @@ -5,7 +5,7 @@ import { Form, Field } from 'react-redux-form'; import { FormGroup, Select } from 'app/ReactReduxForms'; import { elasticLanguages } from 'shared/language'; -import t from 'app/I18N/t'; +import { t } from 'app/I18N'; import ShowIf from 'app/App/ShowIf'; export class AttachmentForm extends Component { diff --git a/app/react/Attachments/components/UploadAttachment.js b/app/react/Attachments/components/UploadAttachment.js index 7b033a6bab..c6a0648dd0 100644 --- a/app/react/Attachments/components/UploadAttachment.js +++ b/app/react/Attachments/components/UploadAttachment.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import t from 'app/I18N/t'; +import { t } from 'app/I18N'; import { Icon } from 'UI'; import { uploadAttachment } from '../actions/actions'; diff --git a/app/react/Attachments/components/specs/__snapshots__/AttachmentsList.spec.js.snap b/app/react/Attachments/components/specs/__snapshots__/AttachmentsList.spec.js.snap index 53f6e31efe..feaa909232 100644 --- a/app/react/Attachments/components/specs/__snapshots__/AttachmentsList.spec.js.snap +++ b/app/react/Attachments/components/specs/__snapshots__/AttachmentsList.spec.js.snap @@ -23,9 +23,9 @@ exports[`AttachmentsList When parent is Target Document should treat all Attachm } >

- + Supporting files - +

@@ -58,9 +58,9 @@ exports[`AttachmentsList should render a sorted list of attachments (files) 1`] } >

- + Supporting files - +

- + Supporting files - +

- + Supporting files - +

- + Supporting files - +

diff --git a/app/react/Attachments/components/specs/__snapshots__/AttachmentsModal.spec.tsx.snap b/app/react/Attachments/components/specs/__snapshots__/AttachmentsModal.spec.tsx.snap index c009fe2f1a..1f6d43f296 100644 --- a/app/react/Attachments/components/specs/__snapshots__/AttachmentsModal.spec.tsx.snap +++ b/app/react/Attachments/components/specs/__snapshots__/AttachmentsModal.spec.tsx.snap @@ -12,7 +12,7 @@ exports[`Attachments Modal Attachment from web Should match render of web form 1 >

Supporting files @@ -38,7 +38,7 @@ exports[`Attachments Modal Attachment from web Should match render of web form 1 Cancel @@ -62,7 +62,7 @@ exports[`Attachments Modal Attachment from web Should match render of web form 1 style="" > Upload from computer @@ -77,7 +77,7 @@ exports[`Attachments Modal Attachment from web Should match render of web form 1 style="font-weight: bold;" > Add from web @@ -155,7 +155,7 @@ exports[`Attachments Modal Attachment from web Should match render of web form 1   Add from URL @@ -181,7 +181,7 @@ exports[`Attachments Modal Should match render of upload form 1`] = ` >

Supporting files @@ -207,7 +207,7 @@ exports[`Attachments Modal Should match render of upload form 1`] = ` Cancel @@ -231,7 +231,7 @@ exports[`Attachments Modal Should match render of upload form 1`] = ` style="font-weight: bold;" > Upload from computer @@ -245,7 +245,7 @@ exports[`Attachments Modal Should match render of upload form 1`] = ` role="tab" > Add from web @@ -286,7 +286,7 @@ exports[`Attachments Modal Should match render of upload form 1`] = `   Upload and select file @@ -300,7 +300,7 @@ exports[`Attachments Modal Should match render of upload form 1`] = ` class="attachments-modal__dropzone-title" > Drag and drop file in this window to upload diff --git a/app/react/Attachments/components/specs/__snapshots__/WebMediaResourceForm.spec.tsx.snap b/app/react/Attachments/components/specs/__snapshots__/WebMediaResourceForm.spec.tsx.snap index 7a728abf7e..e181110ade 100644 --- a/app/react/Attachments/components/specs/__snapshots__/WebMediaResourceForm.spec.tsx.snap +++ b/app/react/Attachments/components/specs/__snapshots__/WebMediaResourceForm.spec.tsx.snap @@ -40,9 +40,9 @@ exports[`Should match render of wem media form 1`] = ` icon="link" />   - + Add from URL - + `; @@ -105,9 +105,9 @@ exports[`should also display a name field when hasName is true 1`] = ` icon="link" />   - + Add from URL - + `; diff --git a/app/react/ConnectionsList/components/specs/ConnectionsGroup.spec.js b/app/react/ConnectionsList/components/specs/ConnectionsGroup.spec.js index 63527f9028..5cb30aefeb 100644 --- a/app/react/ConnectionsList/components/specs/ConnectionsGroup.spec.js +++ b/app/react/ConnectionsList/components/specs/ConnectionsGroup.spec.js @@ -1,9 +1,7 @@ -import { fromJS as Immutable } from 'immutable'; import React from 'react'; - +import { fromJS as Immutable } from 'immutable'; import { shallow } from 'enzyme'; import ShowIf from 'app/App/ShowIf'; - import { ConnectionsGroup } from '../ConnectionsGroup'; describe('ConnectionsGroup', () => { @@ -58,12 +56,16 @@ describe('ConnectionsGroup', () => { expect(subItem1.props().title).toBe('template 1'); expect(subItem1.find('input').props().checked).toBe(false); - expect(subItem1.find('.multiselectItem-name').text()).toBe('template 1'); + expect(subItem1.find('.multiselectItem-name').children().children().text()).toBe( + 'template 1' + ); expect(subItem1.find('.multiselectItem-results').text()).toBe('1'); expect(subItem2.props().title).toBe('template 2'); expect(subItem2.find('input').props().checked).toBe(false); - expect(subItem2.find('.multiselectItem-name').text()).toBe('template 2'); + expect(subItem2.find('.multiselectItem-name').children().children().text()).toBe( + 'template 2' + ); expect(subItem2.find('.multiselectItem-results').text()).toBe('2'); }); diff --git a/app/react/Documents/components/specs/__snapshots__/DocumentContentSnippets.spec.js.snap b/app/react/Documents/components/specs/__snapshots__/DocumentContentSnippets.spec.js.snap index d78695d607..b956b0df0b 100644 --- a/app/react/Documents/components/specs/__snapshots__/DocumentContentSnippets.spec.js.snap +++ b/app/react/Documents/components/specs/__snapshots__/DocumentContentSnippets.spec.js.snap @@ -5,7 +5,11 @@ exports[`SnippetList should render all document snippets 1`] = `
  • - Document contents + + Document contents +
  • - + Edit - + `; diff --git a/app/react/Documents/components/specs/__snapshots__/MetadataFieldSnippets.spec.js.snap b/app/react/Documents/components/specs/__snapshots__/MetadataFieldSnippets.spec.js.snap index 618af0de66..43f296cb90 100644 --- a/app/react/Documents/components/specs/__snapshots__/MetadataFieldSnippets.spec.js.snap +++ b/app/react/Documents/components/specs/__snapshots__/MetadataFieldSnippets.spec.js.snap @@ -8,7 +8,11 @@ exports[`SnippetList should properly render title snippets with Title label as h - Summary + + Summary +
  • - Summary + + Summary +
  • - Search text + + Search text +

    - Search text description + + Search text description +

    `; @@ -24,10 +32,18 @@ exports[`SearchText blankState when there is search term should render a no matc icon="search" />

    - No text match + + No text match +

    - No text match description + + No text match description +

    `; diff --git a/app/react/Forms/components/specs/Switcher.spec.tsx b/app/react/Forms/components/specs/Switcher.spec.tsx index 8d5906823a..1a09c57a4d 100644 --- a/app/react/Forms/components/specs/Switcher.spec.tsx +++ b/app/react/Forms/components/specs/Switcher.spec.tsx @@ -40,7 +40,7 @@ describe('Switcher', () => { it('should receive alternative elements for values', () => { render({ leftLabel: ALL, rightLabel: NONE }); - const labels = component.find('Connect(Translate)'); + const labels = component.find('Translate'); expect(labels.at(0).props().children).toEqual('ALL'); expect(labels.at(1).props().children).toEqual('NONE'); }); @@ -54,7 +54,7 @@ describe('Switcher', () => { it('should render default labels AND/OR', () => { render(); - const labels = component.find('Connect(Translate)'); + const labels = component.find('Translate'); expect(labels.at(0).props().children).toEqual('AND'); expect(labels.at(1).props().children).toEqual('OR'); }); diff --git a/app/react/Forms/components/specs/__snapshots__/DateRange.spec.js.snap b/app/react/Forms/components/specs/__snapshots__/DateRange.spec.js.snap index 224a91aca5..cb1d26c690 100644 --- a/app/react/Forms/components/specs/__snapshots__/DateRange.spec.js.snap +++ b/app/react/Forms/components/specs/__snapshots__/DateRange.spec.js.snap @@ -8,11 +8,11 @@ exports[`DateRange should allow using the local timezone 1`] = ` - From: - + - To: - + - From: - + - To: - + - No options found + + No options found +
  • -5 - + x more - + @@ -526,9 +530,9 @@ exports[`MultiSelect should not render an empty group 1`] = ` -2 - + x more - + diff --git a/app/react/I18N/I18NLinkV2.tsx b/app/react/I18N/I18NLinkV2.tsx new file mode 100644 index 0000000000..a418caa87f --- /dev/null +++ b/app/react/I18N/I18NLinkV2.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { useAtomValue } from 'jotai'; +import { NavLinkProps, NavLink } from 'react-router-dom'; +import { localeAtom } from 'V2/atoms'; + +type I18NLinkProps = NavLinkProps & { to: string; activeClassname?: string }; + +const I18NLink = (props: I18NLinkProps) => { + const { to: link, className, activeClassname, ...rest } = props; + const locale = useAtomValue(localeAtom); + const parsedLink = link.startsWith('/') ? link.slice(1) : link; + const to = locale ? `/${locale}/${parsedLink}` : `/${parsedLink}`; + + return ( + `${className || ''} ${isActive ? activeClassname : ''}`} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} + /> + ); +}; + +export { I18NLink }; diff --git a/app/react/I18N/Translate.tsx b/app/react/I18N/Translate.tsx new file mode 100644 index 0000000000..dca43aba2f --- /dev/null +++ b/app/react/I18N/Translate.tsx @@ -0,0 +1,82 @@ +/* eslint-disable max-statements */ +import React, { Fragment, ReactNode } from 'react'; +import { useAtom, useAtomValue } from 'jotai'; +import { translationsAtom, inlineEditAtom, localeAtom } from 'V2/atoms'; + +const parseMarkdownMarker = ( + line: string, + regexp: RegExp, + wrapper: (text: string) => ReactNode +) => { + const matches = line.match(regexp); + if (matches == null) { + return matches; + } + const parts = matches.input?.split(matches[0]); + return ( + <> + {parts?.length && parts[0]} + {wrapper(matches[1])} + {parts?.length && parts[1]} + + ); +}; + +const parseMarkdownBoldMarker = (line: string) => + parseMarkdownMarker(line, /\*{2}(.*)\*{2}/, text => {text}); + +const parseMarkdownItalicMarker = (line: string) => + parseMarkdownMarker(line, /\*(.*)\*/, text => {text}); + +type TranslateProps = { + className?: string; + children?: string; + context?: string; + translationKey?: string; +}; + +const Translate = ({ className, children, context = 'System', translationKey }: TranslateProps) => { + const translations = useAtomValue(translationsAtom); + const locale = useAtomValue(localeAtom); + const [inlineEditState, setInlineEditState] = useAtom(inlineEditAtom); + + const language = translations.find(translation => translation.locale === locale); + const activeClassName = inlineEditState.inlineEdit ? 'translation active' : 'translation'; + + const translationContext = language?.contexts.find(ctx => ctx.id === context) || { values: {} }; + const text = translationContext.values[(translationKey || children)!] || children; + const lines = text ? text.split('\n') : []; + + return ( + { + if (inlineEditState.inlineEdit) { + event.stopPropagation(); + event.preventDefault(); + setInlineEditState({ + inlineEdit: inlineEditState.inlineEdit, + context, + translationKey: (translationKey || children)!, + }); + } + }} + className={`${activeClassName} ${className || ''}`} + > + {lines.map((line, index) => { + const boldMatches = parseMarkdownBoldMarker(line); + const italicMatches = parseMarkdownItalicMarker(line); + return ( + + {boldMatches || + italicMatches || ( // eslint-disable-next-line react/jsx-no-useless-fragment + <>{line} + )} + {index < lines.length - 1 &&
    } +
    + ); + })} +
    + ); +}; + +export { Translate }; diff --git a/app/react/I18N/TranslateModal.tsx b/app/react/I18N/TranslateModal.tsx new file mode 100644 index 0000000000..336bfa78f3 --- /dev/null +++ b/app/react/I18N/TranslateModal.tsx @@ -0,0 +1,131 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; +import { useFieldArray, useForm } from 'react-hook-form'; +import { FetchResponseError } from 'shared/JSONRequest'; +import { Modal } from 'V2/Components/UI'; +import { settingsAtom, translationsAtom, inlineEditAtom, notificationAtom } from 'V2/atoms'; +import { InputField } from 'app/V2/Components/Forms'; +import { Button } from 'V2/Components/UI/Button'; +import { TranslationValue } from 'V2/shared/types'; +import { postV2 } from 'V2/api/translations'; +import { t } from './translateFunction'; + +const TranslateModal = () => { + const [inlineEditState, setInlineEditState] = useAtom(inlineEditAtom); + const [translations] = useAtom(translationsAtom); + const setNotifications = useSetAtom(notificationAtom); + const context = translations[0].contexts.find(ctx => ctx.id === inlineEditState.context)!; + const { languages = [] } = useAtomValue(settingsAtom); + + const { + register, + handleSubmit, + control, + reset, + formState: { errors, isDirty, isSubmitting }, + } = useForm<{ data: TranslationValue[] }>({ + mode: 'onSubmit', + }); + + const { fields } = useFieldArray({ control, name: 'data' }); + + React.useEffect(() => { + const initialValues = translations.map(translation => { + const language = languages.find(lang => lang.key === translation.locale)!; + const languageContext = translation.contexts.find(c => c.id === context?.id); + const value = + languageContext?.values[inlineEditState.translationKey] || inlineEditState.translationKey; + return { + language: language.key, + value, + key: inlineEditState.translationKey, + }; + }); + reset({ data: initialValues }); + }, [context, inlineEditState.translationKey, languages, reset, translations]); + + const closeModal = () => { + setInlineEditState({ inlineEdit: true, translationKey: '', context: '' }); + }; + + const submit = async ({ data }: { data: TranslationValue[] }) => { + if (isDirty) { + const response = await postV2(data, context); + if (response === 200) { + setNotifications({ + type: 'success', + text: t('System', 'Translations saved', null, false), + }); + } + if (response instanceof FetchResponseError) { + const message = response.json?.prettyMessage + ? response.json.prettyMessage + : response.message; + setNotifications({ + type: 'error', + text: t('System', 'An error occurred', null, false), + details: message, + }); + } + } + closeModal(); + }; + + return ( + inlineEditState.context && ( +
    +
    + +
    + + {t('System', 'Translate', 'Translate', false)} + + {t('System', 'Close', 'Close', false)} + + + + {fields?.map((field, index) => ( + + {field.language.toUpperCase()} + + } + id={field.id} + key={field.id} + {...register(`data.${index}.value`, { required: true })} + hasErrors={errors.data && errors.data[index] !== undefined} + disabled={isSubmitting} + /> + ))} + + + + + +
    +
    +
    +
    + ) + ); +}; + +export { TranslateModal }; diff --git a/app/react/I18N/actions/I18NActions.js b/app/react/I18N/actions/I18NActions.js index 38f5e911aa..49b4f6a53a 100644 --- a/app/react/I18N/actions/I18NActions.js +++ b/app/react/I18N/actions/I18NActions.js @@ -2,8 +2,8 @@ import { actions as formActions } from 'react-redux-form'; import * as notifications from 'app/Notifications/actions/notificationsActions'; import { store } from 'app/store'; import { RequestParams } from 'app/utils/RequestParams'; +import { t } from 'app/I18N'; import I18NApi from '../I18NApi'; -import t from '../t'; export function inlineEditTranslation(contextId, key) { return dispatch => { diff --git a/app/react/I18N/components/I18N.js b/app/react/I18N/components/I18N.js index 3339a99b4a..72295f0c8f 100644 --- a/app/react/I18N/components/I18N.js +++ b/app/react/I18N/components/I18N.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { connect } from 'react-redux'; -export class I18N extends Component { +class I18NComponent extends Component { render() { const dictionary = this.props.dictionaries.toJS().find(d => d.locale === this.props.locale) || { values: {}, @@ -13,7 +13,7 @@ export class I18N extends Component { } } -I18N.propTypes = { +I18NComponent.propTypes = { children: PropTypes.string, locale: PropTypes.string, dictionaries: PropTypes.object, @@ -21,4 +21,4 @@ I18N.propTypes = { const mapStateToProps = ({ locale, dictionaries }) => ({ locale, dictionaries }); -export default connect(mapStateToProps)(I18N); +export const I18N = connect(mapStateToProps)(I18NComponent); diff --git a/app/react/I18N/components/I18NMenu.tsx b/app/react/I18N/components/I18NMenu.tsx index 0e74a6e592..8d49d472c2 100644 --- a/app/react/I18N/components/I18NMenu.tsx +++ b/app/react/I18N/components/I18NMenu.tsx @@ -1,28 +1,21 @@ +/* eslint-disable react/no-multi-comp */ /* eslint-disable react-hooks/rules-of-hooks */ import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { bindActionCreators, Dispatch } from 'redux'; -import { connect, ConnectedProps } from 'react-redux'; -import { IImmutable } from 'shared/types/Immutable'; +import { Location, useLocation } from 'react-router-dom'; +import { useAtom, useAtomValue } from 'jotai'; +import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/20/solid'; import { LanguagesListSchema } from 'shared/types/commonTypes'; -import { Icon } from 'UI'; -import { actions, Translate, t } from 'app/I18N'; -import { IStore } from 'app/istore'; -import { NeedAuthorization } from 'app/Auth'; +import { NeedAuthorization } from 'V2/Components/UI'; import { useOnClickOutsideElement } from 'app/utils/useOnClickOutsideElementHook'; -import { Location, useLocation } from 'react-router-dom'; +import { inlineEditAtom, localeAtom, settingsAtom, userAtom } from 'V2/atoms'; +import { Translate, t } from 'app/I18N'; const locationSearch = (location: Location) => { const cleanSearch = location.search.split(/page=\d+|&page=\d+/).join(''); return cleanSearch === '?' ? '' : cleanSearch; }; -const prepareValues = ( - languageMap: IImmutable, - locale: string, - location: Location -) => { - const languages: LanguagesListSchema = languageMap.toJS(); - +const prepareValues = (languages: LanguagesListSchema, locale: string, location: Location) => { const selectedLanguage = languages.find(lang => lang.key === locale) || languages.find(lang => lang.default); @@ -37,28 +30,20 @@ const prepareValues = ( return { languages, selectedLanguage, urlLocation, path }; }; -const mapStateToProps = (state: IStore) => ({ - languages: state.settings.collection.get('languages'), - i18nmode: state.inlineEdit.get('inlineEdit'), - locale: state.locale, - user: state.user, -}); +const SVGCircle = ({ fill }: { fill: string }) => ( + + + +); -const mapDispatchToProps = (dispatch: Dispatch<{}>) => - bindActionCreators({ toggleInlineEdit: actions.toggleInlineEdit }, dispatch); +// eslint-disable-next-line max-statements +const I18NMenu = () => { + const [inlineEditState, setInlineEditState] = useAtom(inlineEditAtom); + const locale = useAtomValue(localeAtom); + const user = useAtomValue(userAtom); + const { languages: languageList } = useAtomValue(settingsAtom); -const connector = connect(mapStateToProps, mapDispatchToProps); - -type mappedProps = ConnectedProps; - -const i18NMenuComponent = ({ - languages: languageMap, - i18nmode, - user, - locale, - toggleInlineEdit, -}: mappedProps) => { - if (!languageMap || languageMap.size < 1 || (languageMap!.size <= 1 && !user.get('_id'))) { + if (!languageList || languageList.length < 1 || (languageList.length <= 1 && !user?._id)) { return
    ; } @@ -68,7 +53,7 @@ const i18NMenuComponent = ({ const [dropdownOpen, setDropdownOpen] = useState(false); const { languages, selectedLanguage, path, urlLocation } = prepareValues( - languageMap!, + languageList, locale, location ); @@ -88,21 +73,27 @@ const i18NMenuComponent = ({ return (
  • - {i18nmode && ( + {inlineEditState.inlineEdit && (
    @@ -112,7 +103,7 @@ const i18NMenuComponent = ({ )} - {!i18nmode && ( + {!inlineEditState.inlineEdit && (
      @@ -146,11 +137,19 @@ const i18NMenuComponent = ({ className="live-translate" type="button" onClick={() => { - toggleInlineEdit(); + setInlineEditState({ + inlineEdit: !inlineEditState.inlineEdit, + translationKey: '', + context: '', + }); setDropdownOpen(false); }} > - + {inlineEditState.inlineEdit ? ( + + ) : ( + + )} Live translate @@ -162,6 +161,4 @@ const i18NMenuComponent = ({ ); }; -const container = connector(i18NMenuComponent); - -export { container as i18NMenuComponent }; +export { I18NMenu }; diff --git a/app/react/I18N/components/Translate.js b/app/react/I18N/components/Translate.js deleted file mode 100644 index e5e73dd7c3..0000000000 --- a/app/react/I18N/components/Translate.js +++ /dev/null @@ -1,111 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { actions } from 'app/I18N'; - -const parseMarkdownMarker = (line, regexp, wrapper) => { - const matches = line.match(regexp); - if (matches == null) { - return matches; - } - const parts = matches.input.split(matches[0]); - return ( - <> - {parts[0]} - {wrapper(matches[1])} - {parts[1]} - - ); -}; - -const parseMarkdownBoldMarker = line => - parseMarkdownMarker(line, /\*{2}(.*)\*{2}/, text => {text}); - -const parseMarkdownItalicMarker = line => - parseMarkdownMarker(line, /\*(.*)\*/, text => {text}); - -class Translate extends Component { - static resetCachedTranslation() { - Translate.translation = null; - } - - constructor(props) { - super(props); - this.onClick = this.onClick.bind(this); - } - - onClick(e) { - if (this.props.i18nmode) { - e.stopPropagation(); - e.preventDefault(); - this.props.edit(this.props.context, this.props.translationKey); - } - } - - render() { - const lines = this.props.children ? this.props.children.split('\n') : []; - const className = this.props.i18nmode ? 'translation active' : 'translation'; - return ( - - {lines.map((line, index) => { - const boldMatches = parseMarkdownBoldMarker(line); - const italicMatches = parseMarkdownItalicMarker(line); - return ( - - {boldMatches || - italicMatches || ( // eslint-disable-next-line react/jsx-no-useless-fragment - <>{line} - )} - {index < lines.length - 1 &&
      } -
      - ); - })} -
      - ); - } -} - -Translate.defaultProps = { - i18nmode: false, - context: 'System', - edit: false, - translationKey: '', - className: '', -}; - -Translate.propTypes = { - translationKey: PropTypes.string, - context: PropTypes.string, - edit: PropTypes.func, - i18nmode: PropTypes.bool, - children: PropTypes.string.isRequired, - className: PropTypes.string, -}; - -const mapStateToProps = (state, props) => { - if ( - !Translate.translation || - Translate.translation.locale !== state.locale || - state.inlineEdit.get('inlineEdit') - ) { - const translations = state.translations.toJS(); - Translate.translation = translations.find(t => t.locale === state.locale) || { contexts: [] }; - } - const _ctx = props.context || 'System'; - const key = props.translationKey || props.children; - const context = Translate.translation.contexts.find(ctx => ctx.id === _ctx) || { values: {} }; - const canEditThisValue = _ctx === 'System' || !!context.values[props.children]; - return { - translationKey: key, - children: context.values[key] || props.children, - i18nmode: state.inlineEdit.get('inlineEdit') && canEditThisValue, - }; -}; - -function mapDispatchToProps(dispatch) { - return bindActionCreators({ edit: actions.inlineEditTranslation }, dispatch); -} - -export { mapStateToProps, Translate }; -export default connect(mapStateToProps, mapDispatchToProps)(Translate); diff --git a/app/react/I18N/components/TranslateForm.js b/app/react/I18N/components/TranslateForm.js deleted file mode 100644 index 6c29a61bb8..0000000000 --- a/app/react/I18N/components/TranslateForm.js +++ /dev/null @@ -1,100 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { bindActionCreators } from 'redux'; -import { Form, Field } from 'react-redux-form'; -import { connect } from 'react-redux'; -import { actions, Translate, t } from 'app/I18N'; -import Modal from 'app/Layout/Modal'; -import { FormGroup } from 'app/Forms'; - -export class TranslateForm extends Component { - constructor(props) { - super(props); - this.submit = this.submit.bind(this); - this.cancel = this.cancel.bind(this); - } - - submit(values) { - let translations = this.props.translations.toJS(); - translations = translations.map(translation => { - const { locale } = translation; - const context = translation.contexts.find(c => c.id === this.props.context); - context.values[this.props.value] = values[locale]; - translation.contexts = [context]; - return translation; - }); - this.props.saveTranslations(translations); - this.props.close(); - } - - cancel() { - this.props.close(); - } - - render() { - const translations = this.props.translations.toJS(); - const languages = translations.map(translation => translation.locale); - - return ( - - -

      - Translate -

      -
      - {languages.map(language => ( - - - - - - ))} -
      -
      - - - - - -
      - ); - } -} - -TranslateForm.defaultProps = { - isOpen: false, -}; - -TranslateForm.propTypes = { - saveTranslations: PropTypes.func.isRequired, - close: PropTypes.func.isRequired, - isOpen: PropTypes.bool, - context: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - translations: PropTypes.instanceOf(Object).isRequired, -}; - -export function mapStateToProps(state) { - return { - translations: state.translations, - isOpen: state.inlineEdit.get('showInlineEditForm'), - context: state.inlineEdit.get('context'), - value: state.inlineEdit.get('key'), - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators( - { saveTranslations: actions.saveTranslations, close: actions.closeInlineEditTranslation }, - dispatch - ); -} - -export default connect(mapStateToProps, mapDispatchToProps)(TranslateForm); diff --git a/app/react/I18N/components/specs/I18NMenu.spec.tsx b/app/react/I18N/components/specs/I18NMenu.spec.tsx index f8cbb92073..e6743238da 100644 --- a/app/react/I18N/components/specs/I18NMenu.spec.tsx +++ b/app/react/I18N/components/specs/I18NMenu.spec.tsx @@ -2,63 +2,134 @@ * @jest-environment jsdom */ import React from 'react'; -import { act, fireEvent, RenderResult, screen } from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { MockStoreEnhanced } from 'redux-mock-store'; +import { act, fireEvent, RenderResult, screen, render } from '@testing-library/react'; import { Location, MemoryRouter } from 'react-router-dom'; -import Immutable from 'immutable'; -import { defaultState, renderConnectedContainer } from 'app/utils/test/renderConnected'; -import { i18NMenuComponent as I18NMenu } from '../I18NMenu'; +import { createStore, Provider } from 'jotai'; +import { ClientUserSchema } from 'app/apiResponseTypes'; +import { inlineEditAtom, localeAtom, settingsAtom, userAtom } from 'V2/atoms'; +import { TestAtomStoreProvider } from 'V2/testing'; +import { UserRole } from 'shared/types/userSchema'; +import { LanguageISO6391 } from 'shared/types/commonTypes'; +import { I18NMenu } from '../I18NMenu'; + +const defaultLanguages = [ + { + _id: '1', + label: 'English', + key: 'en' as LanguageISO6391, + localized_label: 'English', + default: true, + }, + { + _id: '2', + label: 'Spanish', + key: 'es' as LanguageISO6391, + localized_label: 'Español', + default: false, + }, +]; + +const users = [ + { _id: 'admin', username: 'admin', role: UserRole.ADMIN, email: '' }, + { _id: 'collab', username: 'collab', role: UserRole.COLLABORATOR, email: '' }, +]; describe('I18NMenu', () => { - let props: any; + const initialEntry: Partial = { pathname: '/library' }; + const inlineEditAtomValue = { inlineEdit: false }; let renderResult: RenderResult; - let store: MockStoreEnhanced; - let location: Partial; - const toggleInlineEditMock = jest.fn(); + let settingsAtomValue = { languages: defaultLanguages }; + let localeAtomValue = 'en'; Reflect.deleteProperty(global.window, 'location'); window.location = { ...window.location, assign: jest.fn() }; + const renderComponent = (user?: ClientUserSchema) => { + renderResult = render( + + + + + + ); + }; + beforeEach(() => { + initialEntry.pathname = '/library'; + localeAtomValue = 'en'; + settingsAtomValue = { languages: defaultLanguages }; + inlineEditAtomValue.inlineEdit = false; jest.clearAllMocks(); - const languages = [ - { _id: '1', key: 'en', label: 'English', localized_label: 'English' }, - { _id: '2', key: 'es', label: 'Spanish', localized_label: 'Español', default: true }, - ]; - - props = { - languages: Immutable.fromJS(languages), - toggleInlineEdit: toggleInlineEditMock, - i18nmode: false, - locale: 'es', - }; + }); + + it('should render the links to the different languages', () => { + renderComponent(); + const links = screen.getAllByRole('link'); + expect(links.map(link => link.getAttribute('href'))).toEqual( + expect.arrayContaining(['/en/library', '/es/library']) + ); + }); - location = { - pathname: '/templates/2452345', - search: '?query=weneedmoreclerics', + it('should not render anything if there is only one language', () => { + settingsAtomValue = { + languages: [ + { _id: '2', label: 'Spanish', key: 'es', localized_label: 'Español', default: true }, + ], }; + renderComponent(); + const links = screen.queryAllByRole('link'); + expect(links.length).toBe(0); }); - const render = (userType?: 'admin' | 'editor' | 'collaborator') => { - const storeUser = userType - ? Immutable.fromJS({ _id: 'user1', role: userType }) - : Immutable.fromJS({}); - - props.user = userType - ? Immutable.fromJS({ _id: 'user1', role: userType }) - : Immutable.fromJS({}); - - ({ renderResult, store } = renderConnectedContainer( - , - () => ({ - ...defaultState, - user: storeUser, - }), - 'MemoryRouter', - [location] - )); - }; + it('should show as active the current locale', async () => { + renderComponent(users[0]); + const [listItem] = renderResult + .getAllByRole('listitem') + .filter(item => item.textContent === 'English'); + expect(listItem.getAttribute('class')).toBe('menuNav-item active'); + }); + + it('should active toggle translation edit mode when clicking Live translate', async () => { + renderComponent(users[0]); + expect(renderResult.container).toMatchSnapshot('before turning on live translate'); + await act(async () => { + fireEvent.click(screen.getByText('Live translate').parentElement!); + }); + expect(renderResult.container).toMatchSnapshot('after turning on live translate'); + }); + + describe('when there is a user', () => { + it('should render then laguages and the live translate option', () => { + renderComponent(users[0]); + expect(renderResult.getByText('Live translate')).toBeInTheDocument(); + }); + + it('should not render live translate for unauthorized users', () => { + renderComponent(users[1]); + expect(renderResult.queryByText('Live translate')).not.toBeInTheDocument(); + }); + + it('should display the language section if there is only one language', () => { + settingsAtomValue = { + languages: [ + { _id: '2', label: 'Spanish', key: 'es', localized_label: 'Español', default: true }, + ], + }; + renderComponent(users[1]); + const links = screen.queryAllByRole('link'); + expect(links.length).toBe(1); + expect(links.map(link => link.getAttribute('href'))).toEqual( + expect.arrayContaining(['/es/library']) + ); + }); + }); describe('Paths', () => { it.each` @@ -71,10 +142,10 @@ describe('I18NMenu', () => { `( 'should create the expected links for $pathName', async ({ locale, currentPath, search, expectedPath }) => { - props.locale = locale; - location.pathname = currentPath; - location.search = search; - render('admin'); + localeAtomValue = locale; + initialEntry.pathname = currentPath; + initialEntry.search = search; + renderComponent(users[0]); const links = screen.getAllByRole('link'); expect(links.map(link => link.getAttribute('href'))).toEqual( expect.arrayContaining([`/en${expectedPath}`, `/es${expectedPath}`]) @@ -83,89 +154,47 @@ describe('I18NMenu', () => { ); }); - it('should return empty if there are no languages', () => { - props.languages = Immutable.fromJS([]); - render('admin'); - expect(screen.queryByText('Live translate')).not.toBeInTheDocument(); - expect(screen.queryByText('English')).not.toBeInTheDocument(); - }); - - it('should not show live transtions for not authorized user', async () => { - render('collaborator'); - expect(screen.queryByText('Live translate')).not.toBeInTheDocument(); - expect(screen.getByText('English')).toBeInTheDocument(); - }); - - it('should show live transtions for authorized user', async () => { - render('editor'); - expect(screen.queryByText('Live translate')).toBeInTheDocument(); - const listItems = screen.getAllByRole('link'); - expect(listItems.map(item => item.textContent)).toEqual(['English', 'Español']); - }); - - it('should show as active the current locale', async () => { - render('admin'); - const [listItem] = screen - .getAllByRole('listitem') - .filter(item => item.textContent === 'Español'); - expect(listItem.getAttribute('class')).toBe('menuNav-item active'); - }); - - it('should not display the language section if there is only one language and no user', () => { - props.languages = Immutable.fromJS([{ _id: '1', key: 'en', label: 'English', default: true }]); - render(); - expect(screen.queryByText('English')).not.toBeInTheDocument(); - }); - - it('should display the language section if there is only one language and a user', () => { - props.languages = Immutable.fromJS([{ _id: '1', key: 'en', label: 'English', default: true }]); - render('collaborator'); - expect(screen.queryByText('English')).toBeInTheDocument(); - expect(screen.getByRole('link').getAttribute('href')).toBe( - '/en/templates/2452345?query=weneedmoreclerics' - ); - }); - - it('should change to a single button when live translating', async () => { - props.i18nmode = true; - render('editor'); - expect(screen.getByRole('button').parentElement!.textContent).toEqual('Live translate'); - const activeIcon = renderResult.container.getElementsByClassName('live-on'); - expect(activeIcon.length).toBe(1); - const listItems = screen.queryAllByRole('link'); - expect(listItems).toEqual([]); - }); - - it('should active toggle translation edit mode when clicking Live translate', async () => { - render('admin'); - await act(async () => { - fireEvent.click(screen.getByText('Live translate').parentElement!); + describe('reloading after language change', () => { + const testStore = createStore(); + testStore.set(userAtom, users[0]); + testStore.set(localeAtom, 'en'); + testStore.set(settingsAtom, settingsAtomValue); + + it('should trigger a reload if the current language is deleted', async () => { + const result = render( + + + + + + ); + + const newSettingsAtomValue = { + languages: [ + { + _id: '2', + label: 'Spanish', + key: 'es' as LanguageISO6391, + localized_label: 'Español', + default: true, + }, + ], + }; + + await act(() => { + testStore.set(settingsAtom, newSettingsAtomValue); + }); + + result.rerender( + + + + + + ); + + expect(window.location.assign).toHaveBeenCalledTimes(1); + expect(window.location.assign).toHaveBeenCalledWith('/library'); }); - expect(toggleInlineEditMock).toBeCalled(); - }); - - it('should trigger a reload if the current language is deleted', async () => { - props.locale = 'en'; - render('admin'); - props.languages = Immutable.fromJS([ - { - _id: '2', - key: 'es', - label: 'Spanish', - localized_label: 'Español', - default: true, - }, - ]); - - renderResult.rerender( - - - - - - ); - - expect(window.location.assign).toHaveBeenCalledTimes(1); - expect(window.location.assign).toHaveBeenCalledWith('/templates/2452345'); }); }); diff --git a/app/react/I18N/components/specs/Translate.spec.js b/app/react/I18N/components/specs/Translate.spec.js deleted file mode 100644 index 3dd194e2ea..0000000000 --- a/app/react/I18N/components/specs/Translate.spec.js +++ /dev/null @@ -1,144 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import Immutable from 'immutable'; -import { Translate, mapStateToProps } from '../Translate'; - -describe('Translate', () => { - let component; - let props; - - beforeEach(() => { - props = { - translationKey: 'Search', - edit: jasmine.createSpy('edit'), - }; - }); - - const render = () => { - component = shallow(Search); - }; - - describe('render', () => { - beforeEach(render); - it('should render the property children inside of a span', () => { - expect(component.find('span').text()).toBe('Search'); - }); - - describe('when i18nmode is true', () => { - beforeEach(() => { - props.i18nmode = true; - render(); - }); - - it('should render the span with class active', () => { - expect(component.find('span').hasClass('active')).toBe(true); - }); - - describe('onClick', () => { - let mockEvent; - beforeEach(() => { - mockEvent = jasmine.createSpyObj(['stopPropagation', 'preventDefault']); - }); - - it('should stop the event from going up', () => { - component.simulate('click', mockEvent); - expect(mockEvent.stopPropagation).toHaveBeenCalled(); - expect(mockEvent.preventDefault).toHaveBeenCalled(); - }); - - it('should call edit with context and children', () => { - component.simulate('click', mockEvent); - expect(props.edit).toHaveBeenCalledWith('System', 'Search'); - }); - }); - }); - }); - - describe('markdown support', () => { - it('should parse line break in multiline text', () => { - component = shallow( - - {`this - is - multiline - text`} - - ); - expect(component.find('span').html()).toBe( - 'this
      is
      multiline
      text
      ' - ); - }); - - it('should parse a italic and a highlighted text in translation value by line', () => { - component = shallow( - - {`this - is - *an italic* - text and - this is **a highlighted** text. *discarted* - *Markdown* - `} - - ); - expect(component.find('span').html()).toBe( - // eslint-disable-next-line max-len - 'this
      is
      an italic
      text and
      this is a highlighted text. *discarted*
      Markdown
      ' - ); - }); - }); - - describe('resetCachedTranslation', () => { - it('should set null the current catched translation', () => { - Translate.translation = 'some catched translation'; - Translate.resetCachedTranslation(); - expect(Translate.translation).toBe(null); - }); - }); - - describe('mapStateToProps', () => { - let translations; - beforeEach(() => { - translations = [ - { locale: 'en', contexts: [{ id: 'System', values: { Search: 'Search' } }] }, - { locale: 'es', contexts: [{ id: 'System', values: { Search: 'Buscar' } }] }, - ]; - }); - - it('should try to translate the children and pass it on children', () => { - props = { children: 'Search', context: 'System' }; - const state = { - locale: 'es', - inlineEdit: Immutable.fromJS({ inlineEdit: true }), - translations: Immutable.fromJS(translations), - }; - expect(mapStateToProps(state, props).children).toBe('Buscar'); - expect(mapStateToProps(state, props).i18nmode).toBe(true); - }); - - it('should allow overriding translation key', () => { - props = { children: 'Test', translationKey: 'Search', context: 'System' }; - const state = { - locale: 'es', - inlineEdit: Immutable.fromJS({ inlineEdit: true }), - translations: Immutable.fromJS(translations), - }; - expect(mapStateToProps(state, props).children).toBe('Buscar'); - expect(mapStateToProps(state, props).i18nmode).toBe(true); - }); - - it('should store the current locale translation to be fast', () => { - props = { children: 'Search', context: 'System' }; - const state = { - locale: 'es', - inlineEdit: Immutable.fromJS({ inlineEdit: true }), - translations: Immutable.fromJS(translations), - }; - mapStateToProps(state, props); - expect(Translate.translation.locale).toBe('es'); - state.locale = 'en'; - mapStateToProps(state, props); - expect(Translate.translation.locale).toBe('en'); - }); - }); -}); diff --git a/app/react/I18N/components/specs/TranslateForm.spec.js b/app/react/I18N/components/specs/TranslateForm.spec.js deleted file mode 100644 index 78e79d9405..0000000000 --- a/app/react/I18N/components/specs/TranslateForm.spec.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import Immutable from 'immutable'; -import { Form, Field } from 'react-redux-form'; -import Modal from 'app/Layout/Modal'; -import { FormGroup } from 'app/Forms'; -import { TranslateForm } from '../TranslateForm'; - -describe('TranslateForm', () => { - let component; - let props; - const translations = Immutable.fromJS([ - { - locale: 'en', - contexts: [ - { id: 'System', values: { Search: 'Search', Find: 'Find' } }, - { id: '123', values: {} }, - ], - }, - { - locale: 'es', - contexts: [ - { id: 'System', values: { Search: 'Buscar', Find: 'Encontrar' } }, - { id: '123', values: {} }, - ], - }, - ]); - - beforeEach(() => { - props = { - saveTranslations: jasmine.createSpy('saveTranslations'), - close: jasmine.createSpy('close'), - isOpen: true, - context: 'System', - value: 'Search', - translations, - }; - }); - - const render = () => { - component = shallow(); - }; - - describe('render', () => { - beforeEach(render); - it('should render a Modal and pass isopen property', () => { - expect(component.find(Modal).props().isOpen).toBe(true); - }); - - it('should render a LocalForm and pass submit function and initialState', () => { - expect(component.find(Form).props().onSubmit).toBe(component.instance().submit); - expect(component.find(Form).props().model).toBe('inlineEditModel'); - }); - - it('should redner a FormGroup for each language', () => { - expect(component.find(FormGroup).at(0).props().model).toBe('.en'); - expect(component.find(FormGroup).at(1).props().model).toBe('.es'); - }); - - it('should redner a Field for each language', () => { - expect(component.find(Field).at(0).props().model).toBe('.en'); - expect(component.find(Field).at(1).props().model).toBe('.es'); - }); - }); - - describe('cancel', () => { - it('should call props.close()', () => { - render(); - component.instance().cancel(); - expect(props.close).toHaveBeenCalled(); - }); - }); - - describe('submit', () => { - it('should call saveTranslations with only the updated context', () => { - const expectedTranslations = [ - { - locale: 'en', - contexts: [{ id: 'System', values: { Search: 'Search en', Find: 'Find' } }], - }, - { - locale: 'es', - contexts: [{ id: 'System', values: { Search: 'Buscar es', Find: 'Encontrar' } }], - }, - ]; - render(); - component.instance().submit({ en: 'Search en', es: 'Buscar es' }); - expect(props.saveTranslations).toHaveBeenCalledWith(expectedTranslations); - }); - - it('should call props.close()', () => { - render(); - component.instance().submit({ en: 'Search en', es: 'Buscar es' }); - expect(props.close).toHaveBeenCalled(); - }); - }); -}); diff --git a/app/react/I18N/components/specs/__snapshots__/I18NMenu.spec.tsx.snap b/app/react/I18N/components/specs/__snapshots__/I18NMenu.spec.tsx.snap new file mode 100644 index 0000000000..b1c65982ba --- /dev/null +++ b/app/react/I18N/components/specs/__snapshots__/I18NMenu.spec.tsx.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`I18NMenu should active toggle translation edit mode when clicking Live translate: after turning on live translate 1`] = ` +
      + +
      +`; + +exports[`I18NMenu should active toggle translation edit mode when clicking Live translate: before turning on live translate 1`] = ` +
      + +
      +`; diff --git a/app/react/I18N/index.js b/app/react/I18N/index.js deleted file mode 100644 index 28c9b81aff..0000000000 --- a/app/react/I18N/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import { i18NMenuComponent } from './components/I18NMenu'; -import I18NLink from './components/I18NLink'; -import I18N from './components/I18N'; -import Translate from './components/Translate'; -import TranslateForm from './components/TranslateForm'; -import t from './t'; -import I18NUtils from './utils'; -import I18NApi from './I18NApi'; -import * as actions from './actions/I18NActions'; - -export { - i18NMenuComponent as I18NMenu, - I18NLink, - I18NUtils, - I18N, - Translate, - TranslateForm, - t, - I18NApi, - actions, -}; diff --git a/app/react/I18N/index.ts b/app/react/I18N/index.ts new file mode 100644 index 0000000000..a8f58f3ea4 --- /dev/null +++ b/app/react/I18N/index.ts @@ -0,0 +1,13 @@ +import I18NLink from './components/I18NLink'; +import I18NUtils from './utils'; +import I18NApi from './I18NApi'; + +export { I18NApi }; +export { I18N } from './components/I18N'; +export { I18NMenu } from './components/I18NMenu'; +export * as actions from './actions/I18NActions'; +export { I18NLink as I18NLinkV2 } from './I18NLinkV2'; +export { Translate } from './Translate'; +export { t } from './translateFunction'; +export { TranslateModal } from './TranslateModal'; +export { I18NLink, I18NUtils }; diff --git a/app/react/I18N/specs/I18NLinkV2.spec.tsx b/app/react/I18N/specs/I18NLinkV2.spec.tsx new file mode 100644 index 0000000000..1ae3d34cbe --- /dev/null +++ b/app/react/I18N/specs/I18NLinkV2.spec.tsx @@ -0,0 +1,55 @@ +/** + * @jest-environment jsdom + */ +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; +import { render, RenderResult } from '@testing-library/react'; +import { BrowserRouter } from 'react-router-dom'; +import { TestAtomStoreProvider } from 'V2/testing'; +import { localeAtom } from 'V2/atoms'; +import { I18NLink } from '../I18NLinkV2'; + +describe('I18NLink', () => { + let renderResult: RenderResult; + let locale = 'fr'; + let to = '/about'; + let activeClassname = ''; + + const renderComponent = () => { + renderResult = render( + + + + My link + + + + ); + }; + + it('renders a link with the locale prefixed', () => { + renderComponent(); + const link = renderResult.getByText('My link'); + expect(link.getAttribute('href')).toBe('/fr/about'); + }); + + it('renders a link without a locale if localeAtom is empty', () => { + locale = ''; + to = '/contact'; + + renderComponent(); + + const link = renderResult.getByText('My link'); + expect(link.getAttribute('href')).toBe('/contact'); + }); + + it('should apply active classname', () => { + activeClassname = 'red'; + to = '/'; + + renderComponent(); + + const link = renderResult.getByText('My link'); + expect(link.getAttribute('class')).toBe(' red'); + }); +}); diff --git a/app/react/I18N/specs/TranslateModal.spec.tsx b/app/react/I18N/specs/TranslateModal.spec.tsx new file mode 100644 index 0000000000..0eaeb20b63 --- /dev/null +++ b/app/react/I18N/specs/TranslateModal.spec.tsx @@ -0,0 +1,138 @@ +/** + * @jest-environment jsdom + */ +import React, { act } from 'react'; +import { fireEvent, render, RenderResult } from '@testing-library/react'; +import { TestAtomStoreProvider } from 'V2/testing'; +import { settingsAtom, translationsAtom, inlineEditAtom, notificationAtom } from 'V2/atoms'; +import * as translationsAPI from 'V2/api/translations'; +import { NotificationsContainer } from 'V2/Components/UI'; +import { TranslateModal } from '../TranslateModal'; +import { languages, translations } from './fixtures'; + +describe('TranslateModal', () => { + let renderResult: RenderResult; + + beforeAll(() => { + jest.spyOn(translationsAPI, 'postV2').mockImplementation(async () => Promise.resolve(200)); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const renderComponent = (inlineEdit: boolean, context: string, translationKey: string) => { + renderResult = render( + + + + + ); + }; + + it('renders the modal with fields for each language', () => { + renderComponent(true, 'System', 'Search'); + const inputFields = renderResult.queryAllByRole('textbox'); + expect(inputFields).toHaveLength(2); + expect(inputFields[0]).toHaveValue('Search'); + expect(inputFields[1]).toHaveValue('Buscar'); + expect(renderResult.getByText('EN')); + expect(renderResult.getByText('ES')); + }); + + it('should close the modal without saving', async () => { + renderComponent(true, 'System', 'Search'); + expect(renderResult.getByText('Translate')); + await act(() => { + fireEvent.click(renderResult.getByText('Cancel')); + }); + expect(renderResult.queryByText('Translate')).not.toBeInTheDocument(); + expect(translationsAPI.postV2).not.toHaveBeenCalled(); + }); + + // eslint-disable-next-line max-statements + it('submits the form with updated values, disables while saving, and closes the modal', async () => { + renderComponent(true, 'System', 'Search'); + + const saveButton = renderResult.getByTestId('save-button'); + const inputFields = renderResult.queryAllByRole('textbox'); + const cancelButton = renderResult.getByText('Cancel'); + + await act(() => { + fireEvent.change(inputFields[1], { target: { value: 'Busqueda' } }); + fireEvent.click(saveButton); + }); + + expect(saveButton).toBeDisabled(); + expect(inputFields[0]).toBeDisabled(); + expect(inputFields[1]).toBeDisabled(); + expect(cancelButton).toBeDisabled(); + + expect(translationsAPI.postV2).toHaveBeenCalledWith( + [ + { language: 'en', value: 'Search', key: 'Search' }, + { language: 'es', value: 'Busqueda', key: 'Search' }, + ], + translations[0].contexts[0] + ); + expect(renderResult.queryByText('Translate')).not.toBeInTheDocument(); + expect(renderResult.queryByText('Translations saved')).toBeInTheDocument(); + }); + + it('should not allow sending empty fields', async () => { + renderComponent(true, 'System', 'Search'); + const inputFields = renderResult.queryAllByRole('textbox'); + const saveButton = renderResult.getByTestId('save-button'); + + await act(() => { + fireEvent.change(inputFields[0], { target: { value: '' } }); + fireEvent.click(saveButton); + }); + + expect(translationsAPI.postV2).not.toHaveBeenCalled(); + }); + + it('should use the default context key if translation does not exist', async () => { + renderComponent(true, 'System', 'This key is not in the database'); + const inputFields = renderResult.queryAllByRole('textbox'); + expect(inputFields[0]).toHaveValue('This key is not in the database'); + expect(inputFields[1]).toHaveValue('This key is not in the database'); + const saveButton = renderResult.getByTestId('save-button'); + + await act(() => { + fireEvent.change(inputFields[0], { target: { value: 'My new key' } }); + fireEvent.change(inputFields[1], { target: { value: 'Nueva llave' } }); + fireEvent.click(saveButton); + }); + + expect(translationsAPI.postV2).toHaveBeenCalledWith( + [ + { language: 'en', value: 'My new key', key: 'This key is not in the database' }, + { language: 'es', value: 'Nueva llave', key: 'This key is not in the database' }, + ], + translations[0].contexts[0] + ); + expect(renderResult.queryByText('Translate')).not.toBeInTheDocument(); + }); + + it('should not save if there are no changes', async () => { + renderComponent(true, 'System', 'Search'); + const saveButton = renderResult.getByTestId('save-button'); + const inputFields = renderResult.queryAllByRole('textbox'); + + await act(() => { + fireEvent.change(inputFields[1], { target: { value: 'Nueva traducción' } }); + fireEvent.change(inputFields[1], { target: { value: 'Buscar' } }); + fireEvent.click(saveButton); + }); + + expect(translationsAPI.postV2).not.toHaveBeenCalled(); + }); +}); diff --git a/app/react/I18N/specs/fixtures.ts b/app/react/I18N/specs/fixtures.ts new file mode 100644 index 0000000000..8e7fc17029 --- /dev/null +++ b/app/react/I18N/specs/fixtures.ts @@ -0,0 +1,64 @@ +import { ClientTranslationSchema } from 'app/istore'; +import { LanguagesListSchema } from 'shared/types/commonTypes'; + +const languages: LanguagesListSchema = [ + { + _id: '1', + label: 'English', + key: 'en', + default: true, + }, + { + _id: '1', + label: 'Spanish', + key: 'es', + }, +]; + +const translations: ClientTranslationSchema[] = [ + { + locale: 'en', + contexts: [ + { + id: 'System', + label: 'System', + values: { + Search: 'Search', + confirmDeleteDocument: 'Are you sure you want to delete this document?', + confirmDeleteEntity: 'Are you sure you want to delete this entity?', + }, + }, + ], + }, + { + locale: 'es', + contexts: [ + { + id: 'System', + label: 'System', + values: { + Search: 'Buscar', + confirmDeleteDocument: '¿Esta seguro que quiere borrar este documento?', + }, + }, + ], + }, +]; + +const updatedTranslations: ClientTranslationSchema[] = [ + translations[0], + { + ...translations[1], + contexts: [ + { + ...translations[1].contexts, + values: { + Search: 'Buscar', + confirmDeleteDocument: 'Actualizado!', + }, + }, + ], + }, +]; + +export { translations, updatedTranslations, languages }; diff --git a/app/react/I18N/specs/t.spec.js b/app/react/I18N/specs/t.spec.js deleted file mode 100644 index 06ba8f2623..0000000000 --- a/app/react/I18N/specs/t.spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import { store } from 'app/store'; -import Immutable from 'immutable'; -import t from '../t'; - -describe('t', () => { - let state; - - beforeEach(() => { - t.resetCachedTranslation(); - const dictionaries = [ - { - locale: 'en', - contexts: [ - { - id: 'System', - label: 'System', - values: { - Search: 'Search', - confirmDeleteDocument: 'Are you sure you want to delete this document?', - confirmDeleteEntity: 'Are you sure you want to delete this entity?', - }, - }, - ], - }, - { - locale: 'es', - contexts: [ - { - id: 'System', - label: 'System', - values: { - Search: 'Buscar', - confirmDeleteDocument: '¿Esta seguro que quiere borrar este documento?', - }, - }, - ], - }, - ]; - - state = { - locale: 'es', - translations: Immutable.fromJS(dictionaries), - user: Immutable.fromJS({ _id: 'abc' }), - }; - - spyOn(store, 'getState').and.returnValue(state); - }); - - it('should return the translation', () => { - expect( - t('System', 'confirmDeleteDocument', 'Are you sure you want to delete this document?') - ).toBe('¿Esta seguro que quiere borrar este documento?'); - }); - - describe('when there is no translation', () => { - it('should return the default text', () => { - expect( - t('System', 'confirmDeleteEntity', 'Are you sure you want to delete this entity?') - ).toBe('Are you sure you want to delete this entity?'); - }); - }); - - describe('only passing context and key', () => { - it('should use it as default text', () => { - expect(t('System', 'not translated', 'not translated')); - }); - }); - - describe('when no context', () => { - it('should throw an error', () => { - spyOn(console, 'warn'); - t(undefined, 'confirmDeleteEntity', 'Are you sure you want to delete this entity?'); - //eslint-disable-next-line no-console - expect(console.warn).toHaveBeenCalled(); - }); - }); -}); diff --git a/app/react/I18N/specs/translateFunction.spec.tsx b/app/react/I18N/specs/translateFunction.spec.tsx new file mode 100644 index 0000000000..ab439a45be --- /dev/null +++ b/app/react/I18N/specs/translateFunction.spec.tsx @@ -0,0 +1,113 @@ +/** + * @jest-environment jsdom + */ +import React from 'react'; +import { act, render, RenderResult } from '@testing-library/react'; +import { Provider } from 'jotai'; +import { localeAtom, translationsAtom, atomStore } from 'V2/atoms'; +import { t } from '../translateFunction'; +import { translations, updatedTranslations } from './fixtures'; + +describe('t function', () => { + let renderResult: RenderResult; + let locale = 'es'; + + const renderEnvironment = (...args: typeof t.arguments) => { + renderResult = render({t(...args)}); + }; + + beforeEach(() => { + atomStore.set(translationsAtom, translations); + atomStore.set(localeAtom, locale); + jest.spyOn(atomStore, 'sub'); + locale = 'es'; + }); + + it('should return the translation component with the translated text and not subscribe to the store', () => { + renderEnvironment( + 'System', + 'confirmDeleteDocument', + 'Are you sure you want to delete this document?' + ); + expect( + renderResult.getByText('¿Esta seguro que quiere borrar este documento?') + ).toBeInTheDocument(); + expect(atomStore.sub).toHaveBeenCalledTimes(3); + expect(t.translation).toBe(undefined); + }); + + describe('no component', () => { + it('should return the translated string and subscribe to the atom store', () => { + renderEnvironment( + 'System', + 'confirmDeleteDocument', + 'Are you sure you want to delete this document?', + false + ); + expect( + renderResult.getByText('¿Esta seguro que quiere borrar este documento?') + ).toBeInTheDocument(); + expect(atomStore.sub).toHaveBeenCalledTimes(4); + expect(t.translation).toEqual({ + contexts: translations[1].contexts, + locale: 'es', + }); + }); + + it('should update translation when the atom updates', async () => { + renderEnvironment( + 'System', + 'confirmDeleteDocument', + 'Are you sure you want to delete this document?', + false + ); + expect( + renderResult.getByText('¿Esta seguro que quiere borrar este documento?') + ).toBeInTheDocument(); + + await act(async () => { + atomStore.set(translationsAtom, updatedTranslations); + }); + + expect(t.translation).toEqual({ + contexts: updatedTranslations[1].contexts, + locale: 'es', + }); + }); + }); + + describe('when there is no translation', () => { + it('should return the default text', () => { + renderEnvironment( + 'System', + 'confirmDeleteEntity', + 'Are you sure you want to delete this entity?', + false + ); + expect( + renderResult.getByText('Are you sure you want to delete this entity?') + ).toBeInTheDocument(); + }); + }); + + describe('only passing context and key', () => { + it('should use it as default text', () => { + renderEnvironment('System', 'not translated', undefined, false); + expect(renderResult.getByText('not translated')).toBeInTheDocument(); + }); + }); + + describe('when no context', () => { + it('should throw an error', () => { + spyOn(console, 'warn'); + renderEnvironment( + undefined, + 'confirmDeleteEntity', + 'Are you sure you want to delete this entity?', + false + ); + //eslint-disable-next-line no-console + expect(console.warn).toHaveBeenCalled(); + }); + }); +}); diff --git a/app/react/I18N/t.js b/app/react/I18N/t.js deleted file mode 100644 index 1715fba354..0000000000 --- a/app/react/I18N/t.js +++ /dev/null @@ -1,35 +0,0 @@ -import { store } from 'app/store'; -import React from 'react'; -import translate, { getLocaleTranslation, getContext } from '../../shared/translate'; -import { Translate } from '.'; - -const testingEnvironment = process.env.NODE_ENV === 'test'; - -const t = (contextId, key, _text, returnComponent = true) => { - if (!contextId) { - // eslint-disable-next-line no-console - console.warn(`You cannot translate "${key}", because context id is "${contextId}"`); - } - - if (returnComponent && !testingEnvironment) { - return {key}; - } - - const text = _text || key; - - if (!t.translation) { - const state = store.getState(); - const translations = state.translations.toJS(); - t.translation = getLocaleTranslation(translations, state.locale); - } - - const context = getContext(t.translation, contextId); - - return translate(context, key, text); -}; - -t.resetCachedTranslation = () => { - t.translation = null; -}; - -export default t; diff --git a/app/react/I18N/translateFunction.tsx b/app/react/I18N/translateFunction.tsx new file mode 100644 index 0000000000..e69713c385 --- /dev/null +++ b/app/react/I18N/translateFunction.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { atomStore, translationsAtom, localeAtom } from 'V2/atoms'; +import translate, { getLocaleTranslation, getContext } from 'shared/translate'; +import { Translate } from './Translate'; + +//return type as any since there is no way to create conditional returns based on parameters +interface TranslationFunction { + (contextId?: string, key?: string, text?: string | null, returnComponent?: boolean): any; + translation?: string; +} + +const t: TranslationFunction = (contextId, key, text, returnComponent = true) => { + let translations; + let locale; + + if (!contextId) { + // eslint-disable-next-line no-console + console.warn(`You cannot translate "${key}", because context id is "${contextId}"`); + } + + if (returnComponent) { + return {key}; + } + + const updateTranslations = () => { + translations = atomStore.get(translationsAtom); + locale = atomStore.get(localeAtom); + t.translation = getLocaleTranslation(translations, locale); + return { translations, locale }; + }; + + updateTranslations(); + + atomStore.sub(translationsAtom, () => { + updateTranslations(); + }); + + const context = getContext(t.translation, contextId); + + return translate(context, key, text || key); +}; + +export { t }; diff --git a/app/react/Layout/DocumentLanguage.js b/app/react/Layout/DocumentLanguage.js index de5e20ae3a..58984bb281 100644 --- a/app/react/Layout/DocumentLanguage.js +++ b/app/react/Layout/DocumentLanguage.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { LanguageUtils } from 'shared/language'; -import t from '../I18N/t'; +import { t } from 'app/I18N'; export class DocumentLanguage extends Component { render() { diff --git a/app/react/Layout/ItemSnippet.js b/app/react/Layout/ItemSnippet.js index d0e9f239ce..76c0df4434 100644 --- a/app/react/Layout/ItemSnippet.js +++ b/app/react/Layout/ItemSnippet.js @@ -5,7 +5,7 @@ import React from 'react'; import SafeHTML from 'app/utils/SafeHTML'; import getFieldLabel from 'app/Templates/utils/getFieldLabel'; -import t from '../I18N/t'; +import { t } from 'app/I18N'; export const ItemSnippet = ({ snippets, onSnippetClick, template }) => { let content; diff --git a/app/react/Layout/TemplateLabel.js b/app/react/Layout/TemplateLabel.js index d53c110cd9..0dbc514aa2 100644 --- a/app/react/Layout/TemplateLabel.js +++ b/app/react/Layout/TemplateLabel.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { COLORS } from 'app/utils/colors'; -import t from '../I18N/t'; +import { t } from 'app/I18N'; const getTemplateInfo = createSelector( s => s.templates, diff --git a/app/react/Layout/specs/DocumentLanguage.spec.js b/app/react/Layout/specs/DocumentLanguage.spec.js index ae7b446cc8..ac2678397b 100644 --- a/app/react/Layout/specs/DocumentLanguage.spec.js +++ b/app/react/Layout/specs/DocumentLanguage.spec.js @@ -3,6 +3,11 @@ import { shallow } from 'enzyme'; import { fromJS as Immutable } from 'immutable'; import { DocumentLanguage, mapStateToProps } from '../DocumentLanguage'; +jest.mock('app/I18N', () => ({ + t: (_context, key) => key, + Translate: ({ children }) => children, +})); + describe('DocumentLanguage', () => { let component; let props; diff --git a/app/react/Layout/specs/__snapshots__/BackButton.spec.js.snap b/app/react/Layout/specs/__snapshots__/BackButton.spec.js.snap index aec210e42c..3a76161b24 100644 --- a/app/react/Layout/specs/__snapshots__/BackButton.spec.js.snap +++ b/app/react/Layout/specs/__snapshots__/BackButton.spec.js.snap @@ -11,7 +11,11 @@ exports[`Icon should render the back button to the provided url 1`] = ` - Back + + Back + `; diff --git a/app/react/Layout/specs/__snapshots__/ConfirmModal.spec.js.snap b/app/react/Layout/specs/__snapshots__/ConfirmModal.spec.js.snap index 2790d4f7f9..f4e52c24eb 100644 --- a/app/react/Layout/specs/__snapshots__/ConfirmModal.spec.js.snap +++ b/app/react/Layout/specs/__snapshots__/ConfirmModal.spec.js.snap @@ -9,14 +9,14 @@ exports[`ConfirmModal should render a confirm modal 1`] = ` >

      - + Confirm action - +

      - + Are you sure you want to continue? - +

      @@ -25,18 +25,18 @@ exports[`ConfirmModal should render a confirm modal 1`] = ` onClick={[Function]} type="button" > - + Cancel - +
      diff --git a/app/react/Layout/specs/__snapshots__/ItemSnippet.spec.js.snap b/app/react/Layout/specs/__snapshots__/ItemSnippet.spec.js.snap index d22facb5c7..28767ea691 100644 --- a/app/react/Layout/specs/__snapshots__/ItemSnippet.spec.js.snap +++ b/app/react/Layout/specs/__snapshots__/ItemSnippet.spec.js.snap @@ -7,7 +7,11 @@ exports[`ItemSnippet should first metadata snippet if exists 1`] = `
      - Title + + Title +
      - Show more + + Show more +
    @@ -34,7 +42,11 @@ exports[`ItemSnippet should first metadata snippet if metadata only has metadata
    - Summary + + Summary +
    - Show more + + Show more +
    @@ -61,7 +77,11 @@ exports[`ItemSnippet should first metadata snippet when there are no document co
    - Title + + Title +
    - Show more + + Show more +
    @@ -88,7 +112,11 @@ exports[`ItemSnippet should first title snippet if metadata only has title snipp
    - Title + + Title +
    - Show more + + Show more +
    @@ -115,7 +147,11 @@ exports[`ItemSnippet should show first document snippet if there are not metadat
    - Document contents + + Document contents +
    - Show more + + Show more +
    diff --git a/app/react/Library/actions/exportActions.ts b/app/react/Library/actions/exportActions.ts index 8eda955c04..b688f4d468 100644 --- a/app/react/Library/actions/exportActions.ts +++ b/app/react/Library/actions/exportActions.ts @@ -72,9 +72,11 @@ const requestHandler = ( .catch(err => { clearState(dispatch); if (err.status === 403) { - dispatch(notify(t('System', 'Invalid captcha'), 'danger')); + dispatch(notify(t('System', 'Invalid captcha', null, false), 'danger')); } else { - dispatch(notify(t('System', 'An error has occurred during data export'), 'danger')); + dispatch( + notify(t('System', 'An error has occurred during data export', null, false), 'danger') + ); } return err; }); diff --git a/app/react/Library/components/specs/DocumentTypesList.spec.js b/app/react/Library/components/specs/DocumentTypesList.spec.js index cf13ce71d2..a9034c955f 100644 --- a/app/react/Library/components/specs/DocumentTypesList.spec.js +++ b/app/react/Library/components/specs/DocumentTypesList.spec.js @@ -2,9 +2,13 @@ import React from 'react'; import { shallow } from 'enzyme'; import Immutable from 'immutable'; - import { DocumentTypesList } from '../DocumentTypesList'; +jest.mock('app/I18N', () => ({ + t: (_context, key) => key, + Translate: ({ children }) => children, +})); + describe('DocumentTypesList', () => { let component; let props; diff --git a/app/react/Library/components/specs/__snapshots__/Doc.spec.js.snap b/app/react/Library/components/specs/__snapshots__/Doc.spec.js.snap index bb71456b61..752e0ecaa6 100644 --- a/app/react/Library/components/specs/__snapshots__/Doc.spec.js.snap +++ b/app/react/Library/components/specs/__snapshots__/Doc.spec.js.snap @@ -54,6 +54,9 @@ exports[`Doc when target reference is specified should pass the target reference icon="exchange-alt" /> + @@ -88,6 +91,9 @@ exports[`Doc when target reference is specified should pass the target reference icon="exchange-alt" /> + diff --git a/app/react/Library/components/specs/__snapshots__/NestedFilter.spec.js.snap b/app/react/Library/components/specs/__snapshots__/NestedFilter.spec.js.snap index fab308b214..6c7c2c359c 100644 --- a/app/react/Library/components/specs/__snapshots__/NestedFilter.spec.js.snap +++ b/app/react/Library/components/specs/__snapshots__/NestedFilter.spec.js.snap @@ -25,9 +25,9 @@ exports[`NestedFilter should render a text filter field with a label and passing >   - + Strict mode - + diff --git a/app/react/Library/components/specs/__snapshots__/QuickLabelHeader.spec.tsx.snap b/app/react/Library/components/specs/__snapshots__/QuickLabelHeader.spec.tsx.snap index f4c3a77266..999ffe41cf 100644 --- a/app/react/Library/components/specs/__snapshots__/QuickLabelHeader.spec.tsx.snap +++ b/app/react/Library/components/specs/__snapshots__/QuickLabelHeader.spec.tsx.snap @@ -18,14 +18,22 @@ exports[`QuickLabelPanel should render with thesaurus 1`] = ` - Back to thesaurus + + Back to thesaurus +

    - Quick labeling for + + Quick labeling for +   thesaurus1 @@ -44,7 +52,11 @@ exports[`QuickLabelPanel should render without thesaurus 1`] = ` className="content-header-title" >

    - Ooops... please go + + Ooops... please go +   - Back to thesauri + + Back to thesauri +

    diff --git a/app/react/Library/components/specs/__snapshots__/QuickLabelPanel.spec.tsx.snap b/app/react/Library/components/specs/__snapshots__/QuickLabelPanel.spec.tsx.snap index 56c9fbcf3d..99b3235532 100644 --- a/app/react/Library/components/specs/__snapshots__/QuickLabelPanel.spec.tsx.snap +++ b/app/react/Library/components/specs/__snapshots__/QuickLabelPanel.spec.tsx.snap @@ -16,9 +16,9 @@ exports[`QuickLabelPanel should render correctly 1`] = ` 2 - + selected - +

  • - + View - + `; @@ -26,9 +26,9 @@ exports[`ViewDocButton should render a view button poiting to the doc url with t icon="angle-right" /> - + View - + `; @@ -42,9 +42,9 @@ exports[`ViewDocButton when targetReference is provided should render view butto icon="angle-right" /> - + View - + `; @@ -58,8 +58,8 @@ exports[`ViewDocButton when targetReference is provided should render view butto icon="angle-right" /> - + View - + `; diff --git a/app/react/Markdown/components/specs/EntityData.spec.tsx b/app/react/Markdown/components/specs/EntityData.spec.tsx index adef62f2e3..72b14bdda4 100644 --- a/app/react/Markdown/components/specs/EntityData.spec.tsx +++ b/app/react/Markdown/components/specs/EntityData.spec.tsx @@ -4,7 +4,9 @@ import React, { act } from 'react'; import { screen, RenderResult } from '@testing-library/react'; import { renderConnectedContainer } from 'app/utils/test/renderConnected'; -import { state } from './fixture/state'; +import { TestAtomStoreProvider } from 'V2/testing'; +import { localeAtom, translationsAtom } from 'V2/atoms'; +import { state, translations } from './fixture/state'; import { EntityData, EntityDataProps } from '../EntityData'; describe('EntityData Markdown', () => { @@ -25,7 +27,17 @@ describe('EntityData Markdown', () => { const render = async (props: EntityDataProps) => { await act(async () => { - ({ renderResult } = renderConnectedContainer(, () => state)); + ({ renderResult } = renderConnectedContainer( + + + , + () => state + )); }); }; diff --git a/app/react/Markdown/components/specs/__snapshots__/ContactForm.spec.js.snap b/app/react/Markdown/components/specs/__snapshots__/ContactForm.spec.js.snap index 329520e05d..3542446a4b 100644 --- a/app/react/Markdown/components/specs/__snapshots__/ContactForm.spec.js.snap +++ b/app/react/Markdown/components/specs/__snapshots__/ContactForm.spec.js.snap @@ -31,9 +31,9 @@ exports[`ContactForm should render the ContactForm 1`] = ` className="form-group-label" htmlFor="name" > - + Name - + @@ -57,9 +57,9 @@ exports[`ContactForm should render the ContactForm 1`] = ` className="form-group-label" htmlFor="email" > - + Email - + @@ -83,9 +83,9 @@ exports[`ContactForm should render the ContactForm 1`] = ` className="form-group-label" htmlFor="message" > - + Message - + @@ -115,9 +115,9 @@ exports[`ContactForm should render the ContactForm 1`] = ` - + Send - + diff --git a/app/react/Markdown/components/specs/__snapshots__/MarkdownMedia.spec.tsx.snap b/app/react/Markdown/components/specs/__snapshots__/MarkdownMedia.spec.tsx.snap index a4af7d19a6..ea160de429 100644 --- a/app/react/Markdown/components/specs/__snapshots__/MarkdownMedia.spec.tsx.snap +++ b/app/react/Markdown/components/specs/__snapshots__/MarkdownMedia.spec.tsx.snap @@ -217,14 +217,14 @@ exports[`MarkdownMedia render uploaded files should render the edition mode 1`] type="button" > Add timelink
    Timelinks diff --git a/app/react/Markdown/components/specs/fixture/state.ts b/app/react/Markdown/components/specs/fixture/state.ts index ad60a33d94..3672ab2b40 100644 --- a/app/react/Markdown/components/specs/fixture/state.ts +++ b/app/react/Markdown/components/specs/fixture/state.ts @@ -40,20 +40,19 @@ const state = { }, ]), thesauris: Immutable.fromJS([{}]), - translations: Immutable.fromJS([ - { - locale: 'en', - contexts: [ - { - id: 't1', - values: { Title: 'Title translated', 'Main Image': 'Main Image translated' }, - }, - ], - }, - ]), settings: { collection: Immutable.fromJS({ newNameGeneration: true }) }, - inlineEdit: Immutable.fromJS({ inlineEdit: false }), - locale: 'en', }; -export { state }; +const translations = [ + { + locale: 'en', + contexts: [ + { + id: 't1', + values: { Title: 'Title translated', 'Main Image': 'Main Image translated' }, + }, + ], + }, +]; + +export { state, translations }; diff --git a/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap b/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap index 79f670d9ca..8db0544671 100644 --- a/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap +++ b/app/react/Markdown/specs/__snapshots__/MarkdownViewer.spec.js.snap @@ -306,11 +306,11 @@ exports[`MarkdownViewer render should render customHook components and show an e
    - Custom component markup error: unsupported values! Please check your configuration - +
    diff --git a/app/react/Metadata/components/specs/__snapshots__/GeolocationViewer.spec.js.snap b/app/react/Metadata/components/specs/__snapshots__/GeolocationViewer.spec.js.snap index 78f252d949..32d98e666a 100644 --- a/app/react/Metadata/components/specs/__snapshots__/GeolocationViewer.spec.js.snap +++ b/app/react/Metadata/components/specs/__snapshots__/GeolocationViewer.spec.js.snap @@ -12,15 +12,15 @@ exports[`GeolocationViewer should not fail if points is just an empty array 1`] className="print-view-alt" >

    - + Latitude - + :

    - + Longitude - + :

    @@ -66,16 +66,16 @@ exports[`GeolocationViewer should render a map with markers when onlyForCards is className="print-view-alt" >

    - + Latitude - + : 13

    - + Longitude - + : 7

    diff --git a/app/react/Metadata/components/specs/__snapshots__/IconField.spec.js.snap b/app/react/Metadata/components/specs/__snapshots__/IconField.spec.js.snap index 16586ee119..ebd6b15b40 100644 --- a/app/react/Metadata/components/specs/__snapshots__/IconField.spec.js.snap +++ b/app/react/Metadata/components/specs/__snapshots__/IconField.spec.js.snap @@ -7,9 +7,9 @@ exports[`IconField should be open when has value 1`] = ` - + remove icon - + @@ -19,9 +19,9 @@ exports[`IconField should be open when has value 1`] = ` open={true} showLabel={ - + add icon - + @@ -33,13 +33,13 @@ exports[`IconField should be open when has value 1`] = ` >
  • - + remove icon - + @@ -73,9 +73,9 @@ exports[`IconField should render IconSelector with toggleDisplay 1`] = ` open={false} showLabel={ - + add icon - + @@ -87,13 +87,13 @@ exports[`IconField should render IconSelector with toggleDisplay 1`] = ` >
  • - Label + + Label +
    - Geolocation Label + + Geolocation Label +
    - label array + + label array +
    - Link + + Link +
    - Media Label + + Media Label +
    - Image Label + + Image Label +
    - label array + + label array +
    - label array + + label array +
    - withUrl + + withUrl +
    - withUrl + + withUrl +
    - metadata without property + + metadata without property +
    - No property + + No property +
    `; @@ -330,7 +378,11 @@ exports[`Metadata should render sorted property with sorted styles 1`] = `
    - sortedBy + + sortedBy +
    - Label + + Label +
    - label + + label +
    - label + + label +
    - label array + + label array +
    + > + +
    diff --git a/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js b/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js index e7dd43429b..166dfd263d 100644 --- a/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js +++ b/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js @@ -1,9 +1,13 @@ import React from 'react'; import { shallow } from 'enzyme'; import { fromJS } from 'immutable'; - import { LoadMoreRelationshipsButton, mapStateToProps } from '../LoadMoreRelationshipsButton'; +jest.mock('app/I18N', () => ({ + t: (_context, key) => key, + Translate: ({ children }) => children, +})); + describe('LoadMoreRelationshipsButton', () => { let component; let props; diff --git a/app/react/Relationships/components/specs/RelationshipsFormButtons.spec.tsx b/app/react/Relationships/components/specs/RelationshipsFormButtons.spec.tsx index a7f54e1c7c..559dfefe31 100644 --- a/app/react/Relationships/components/specs/RelationshipsFormButtons.spec.tsx +++ b/app/react/Relationships/components/specs/RelationshipsFormButtons.spec.tsx @@ -78,7 +78,7 @@ describe('RelationshipsFormButtons', () => { render(user, { editing: false }); const buttons = component.find('button'); expect(buttons.length).toBe(1); - expect(buttons.at(0).find('span').text()).toEqual('Edit'); + expect(buttons.at(0).find('span.translation').text()).toEqual('Edit'); } ); it.each([editorUser, adminUser, collaboratorUser])( @@ -87,8 +87,8 @@ describe('RelationshipsFormButtons', () => { render(user, { editing: true }); const buttons = component.find('button'); expect(buttons.length).toBe(2); - expect(buttons.at(0).find('span').text()).toEqual('Cancel'); - expect(buttons.at(1).find('span').text()).toEqual('Save'); + expect(buttons.at(0).find('span.translation').text()).toEqual('Cancel'); + expect(buttons.at(1).find('span.translation').text()).toEqual('Save'); } ); }); diff --git a/app/react/Relationships/components/specs/__snapshots__/HubRelationshipMetadata.spec.js.snap b/app/react/Relationships/components/specs/__snapshots__/HubRelationshipMetadata.spec.js.snap index 9fd7bd5a97..54df749c77 100644 --- a/app/react/Relationships/components/specs/__snapshots__/HubRelationshipMetadata.spec.js.snap +++ b/app/react/Relationships/components/specs/__snapshots__/HubRelationshipMetadata.spec.js.snap @@ -91,7 +91,11 @@ exports[`HubRelationshipMetadata should render the metadata correctly when text key="text" >
    - Text + + Text +
    - Back to + + Back to + 'Thes' @@ -29,7 +33,11 @@ exports[`EntityViewer should render 1 1`] = ` - Documents including suggestion: + + Documents including suggestion: + 'Topic1' @@ -39,13 +47,21 @@ exports[`EntityViewer should render 1 1`] = ` />
    - Document + + Document + 4 - of + + of + >39 @@ -87,19 +103,31 @@ exports[`EntityViewer should render 2 1`] = ` - Documents for custom filter + + Documents for custom filter +
    - Document + + Document + 1 - of + + of + 10 diff --git a/app/react/SemanticSearch/components/specs/__snapshots__/DocumentResults.spec.js.snap b/app/react/SemanticSearch/components/specs/__snapshots__/DocumentResults.spec.js.snap index 4f73e1727a..16b2742933 100644 --- a/app/react/SemanticSearch/components/specs/__snapshots__/DocumentResults.spec.js.snap +++ b/app/react/SemanticSearch/components/specs/__snapshots__/DocumentResults.spec.js.snap @@ -59,9 +59,9 @@ exports[`DocumentResults render should render results summary and snippets above
    - + Threshold - + 30.00 % @@ -73,9 +73,21 @@ exports[`DocumentResults render should render results summary and snippets above + Precision + + } min={0.3} - minLabel="Exploration" + minLabel={ + + Exploration + + } model=".threshold" step={0.01} /> @@ -86,9 +98,9 @@ exports[`DocumentResults render should render results summary and snippets above className="metadata-type-numeric" >
    - + Number of sentences above threshold - +
    2 @@ -98,9 +110,9 @@ exports[`DocumentResults render should render results summary and snippets above className="metadata-type-numeric" >
    - + % of sentences above threshold - +
    50.00 diff --git a/app/react/SemanticSearch/components/specs/__snapshots__/ResultsFiltersPanel.spec.js.snap b/app/react/SemanticSearch/components/specs/__snapshots__/ResultsFiltersPanel.spec.js.snap index a87445724d..030d32035e 100644 --- a/app/react/SemanticSearch/components/specs/__snapshots__/ResultsFiltersPanel.spec.js.snap +++ b/app/react/SemanticSearch/components/specs/__snapshots__/ResultsFiltersPanel.spec.js.snap @@ -11,7 +11,11 @@ exports[`ResultsFiltersPanel render should render search filters and instruction
    - Fine tune + + Fine tune +
    - Threshold + + Threshold + 80.00%
    @@ -32,9 +40,21 @@ exports[`ResultsFiltersPanel render should render search filters and instruction + Precision + + } min={0.3} - minLabel="Exploration" + minLabel={ + + Exploration + + } model=".threshold" prefix="threshold" step={0.01} @@ -46,16 +66,32 @@ exports[`ResultsFiltersPanel render should render search filters and instruction key="Minimum relevant sentences per document" >
    - Minimum relevant sentences per document + + Minimum relevant sentences per document +
    + + + } min={1} - minLabel="" + minLabel={ + + + + } model=".minRelevantSentences" prefix="minRelevantSentences" step={1} @@ -75,25 +111,25 @@ exports[`ResultsFiltersPanel render should render search filters and instruction size="2x" />

    - Semantic search is a technique to provide contextual results. Its ability to capture concepts and word associations in human language enables the retrieval of related information such as synonyms, connected categories or entities, etc. . - +

    - The threshold determines how close the results match the search concept. Move the slider to the right to narrow down the concept of the search query. The obtained results will be more precise. Move the slider to the left to more broaden the concept and explore related content. - +

    - Semantic search is applied to each sentence in a document. Filter the documents by the minimum number of sentences that exceed the threshold. - +

    diff --git a/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchMultieditPanel.spec.js.snap b/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchMultieditPanel.spec.js.snap index 628dcd9b04..5c362bf7b7 100644 --- a/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchMultieditPanel.spec.js.snap +++ b/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchMultieditPanel.spec.js.snap @@ -51,17 +51,17 @@ exports[`SemanticSearchMultieditPanel open should not open side panel if there a size="2x" />

    - + Warning: you are editing multiple entities. Fields marked with a - + - + will be updated with the same value. - +

    - Cancel + + Cancel + @@ -153,17 +161,17 @@ exports[`SemanticSearchMultieditPanel should render multi edit form for semantic size="2x" />

    - + Warning: you are editing multiple entities. Fields marked with a - + - + will be updated with the same value. - +

    - Cancel + + Cancel + diff --git a/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchResults.spec.js.snap b/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchResults.spec.js.snap index 46235b5664..a55ad71855 100644 --- a/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchResults.spec.js.snap +++ b/app/react/SemanticSearch/components/specs/__snapshots__/SemanticSearchResults.spec.js.snap @@ -17,9 +17,9 @@ exports[`SemanticSearchResults should render results in ItemList 1`] = ` >

    - + Semantic search - + :   - + Edit all documents matching this criteria - +

    - + documents - +
    @@ -66,16 +66,16 @@ exports[`SemanticSearchResults should render results in ItemList 1`] = ` className="metadata-type-text" >
    - + Sentences above threshold - +
    2 - + out of - + ( 50.00 @@ -117,16 +117,16 @@ exports[`SemanticSearchResults should render results in ItemList 1`] = ` className="metadata-type-text" >
    - + Sentences above threshold - +
    1 - + out of - + ( 40.00 @@ -169,18 +169,18 @@ exports[`SemanticSearchResults should render results in ItemList 1`] = ` - + of - + 0 - + documents - +

    - + x more - +
    @@ -215,9 +215,9 @@ exports[`SemanticSearchResults when the search is empty should render not found className="row panels-layout" >

    - + Search not found - +

    ({ ...thesaurus, - name: t(thesaurus._id, thesaurus.name, null, false), + name: t(thesaurus._id!.toString(), thesaurus.name, null, false), })), 'name' ); diff --git a/app/react/Templates/components/specs/FormConfigSelect.spec.tsx b/app/react/Templates/components/specs/FormConfigSelect.spec.tsx index 8aa0ebf1c6..36c8b9e53b 100644 --- a/app/react/Templates/components/specs/FormConfigSelect.spec.tsx +++ b/app/react/Templates/components/specs/FormConfigSelect.spec.tsx @@ -3,14 +3,41 @@ */ import React from 'react'; import Immutable from 'immutable'; -import { screen, RenderResult } from '@testing-library/react'; +import { screen, RenderResult, act } from '@testing-library/react'; import { Provider } from 'react-redux'; +import { Provider as AtomProvider } from 'jotai'; import { MockStoreEnhanced } from 'redux-mock-store'; import { defaultState, renderConnectedContainer } from 'app/utils/test/renderConnected'; -import { store } from 'app/store'; -import { t } from 'app/I18N'; +import { atomStore, localeAtom, translationsAtom } from 'V2/atoms'; +import { ClientTranslationSchema } from 'app/istore'; import { FormConfigSelect } from '../FormConfigSelect'; +const translations: ClientTranslationSchema[] = [ + { + locale: 'es', + contexts: [ + { + _id: '1', + id: '1', + label: 'Thesauri 1', + values: { + 'Thesauri 1': 'Diccionario B', + }, + type: 'Thesaurus', + }, + { + _id: '2', + id: '2', + label: 'Thesauri 2', + values: { + 'Thesauri 2': 'Diccionario A', + }, + type: 'Thesaurus', + }, + ], + }, +]; + const defineTemplateInStore = ( content: string, _id?: string, @@ -36,11 +63,11 @@ describe('FormConfigSelect', () => { let renderResult: RenderResult; let reduxStore: MockStoreEnhanced; let state: any; + let locale = 'en'; beforeEach(() => { state = { ...defaultState, - locale: 'en', template: { ...defineTemplateInStore('1', 'id1') }, thesauris: Immutable.fromJS([ { _id: '1', values: [], name: 'Thesauri 1' }, @@ -49,37 +76,17 @@ describe('FormConfigSelect', () => { templates: Immutable.fromJS([ { properties: [{ content: '1', type: 'select' }], name: 'template1' }, ]), - translations: Immutable.fromJS([ - { - locale: 'es', - contexts: [ - { - _id: '1', - id: '1', - label: 'Thesauri 1', - values: { - 'Thesauri 1': 'Diccionario B', - }, - type: 'Thesaurus', - }, - { - _id: '2', - id: '2', - label: 'Thesauri 2', - values: { - 'Thesauri 2': 'Diccionario A', - }, - type: 'Thesaurus', - }, - ], - }, - ]), }; }); const render = () => { + atomStore.set(translationsAtom, translations); + atomStore.set(localeAtom, locale); + ({ store: reduxStore, renderResult } = renderConnectedContainer( - , + + + , () => state )); }; @@ -92,9 +99,7 @@ describe('FormConfigSelect', () => { }); it('should render the select with the sorted translated dictionaries', () => { - state.locale = 'es'; - jest.spyOn(store!, 'getState').mockImplementationOnce(() => ({ ...state })); - t.resetCachedTranslation(); + locale = 'es'; render(); const options = screen.getAllByText('Diccionario', { exact: false }); @@ -103,17 +108,17 @@ describe('FormConfigSelect', () => { }); describe('validation', () => { - it('should show a warning when changing the select thesaurus', () => { - t.resetCachedTranslation(); + it('should show a warning when changing the select thesaurus', async () => { render(); state = { ...state, template: { ...defineTemplateInStore('2', 'id1') } }; - renderResult.rerender( - - - - ); + await act(() => { + reduxStore.dispatch({ + type: 'rrf/change', + value: 'id1', + }); + }); const warning = screen.queryByText( 'By making this change, any values from the previous thesaurus', diff --git a/app/react/Templates/components/specs/TemplateAsPageControl.spec.tsx b/app/react/Templates/components/specs/TemplateAsPageControl.spec.tsx index 84380c3862..ff6148d33b 100644 --- a/app/react/Templates/components/specs/TemplateAsPageControl.spec.tsx +++ b/app/react/Templates/components/specs/TemplateAsPageControl.spec.tsx @@ -10,6 +10,11 @@ import { TemplateAsPageControl } from '../TemplateAsPageControl'; const middlewares = [thunk]; +jest.mock('app/I18N', () => ({ + t: (_context: string, key: string) => key, + Translate: ({ children }: { children: React.ReactElement }) => children, +})); + describe('TemplateAsPageControl', () => { const mockStoreCreator: MockStoreCreator = configureStore(middlewares); let component: ShallowWrapper; diff --git a/app/react/Templates/components/specs/__snapshots__/FormConfigMultimedia.spec.js.snap b/app/react/Templates/components/specs/__snapshots__/FormConfigMultimedia.spec.js.snap index 0a7333779f..0dd51cf8d0 100644 --- a/app/react/Templates/components/specs/__snapshots__/FormConfigMultimedia.spec.js.snap +++ b/app/react/Templates/components/specs/__snapshots__/FormConfigMultimedia.spec.js.snap @@ -6,9 +6,9 @@ exports[`FormConfigMultimedia should allow excluding "required" 1`] = ` className="form-group" > - + This property will be shown without the label. - + - + This property will be shown using all the width available. - + - + This property will appear in the library cards as part of the basic info. - +
    @@ -53,9 +53,9 @@ exports[`FormConfigMultimedia should allow excluding "required" 1`] = ` className="form-group" >

    - + Fit - +   - will show the entire media inside the container. - +
    - + Fill - +   - will attempt to fill the container, using its entire width. In cards, cropping is likely to occur. - +

    @@ -230,9 +230,9 @@ exports[`FormConfigMultimedia should allow excluding "style" 1`] = ` className="form-group" > - + This property will be shown without the label. - + - + This property will be shown using all the width available. - + - You won't be able to save an entity if this property is empty. - + - + This property will appear in the library cards as part of the basic info. - + - + This property will be shown without the label. - + - + This property will be shown using all the width available. - + - You won't be able to save an entity if this property is empty. - + - + This property will appear in the library cards as part of the basic info. - +
    @@ -365,9 +365,9 @@ exports[`FormConfigMultimedia should allow setting a help text 1`] = ` className="form-group" >

    - + Fit - +   - will show the entire media inside the container. - +
    - + Fill - +   - will attempt to fill the container, using its entire width. In cards, cropping is likely to occur. - +

    diff --git a/app/react/Templates/components/specs/__snapshots__/FormConfigRelationship.spec.js.snap b/app/react/Templates/components/specs/__snapshots__/FormConfigRelationship.spec.js.snap index 40a1358af8..fc256811f7 100644 --- a/app/react/Templates/components/specs/__snapshots__/FormConfigRelationship.spec.js.snap +++ b/app/react/Templates/components/specs/__snapshots__/FormConfigRelationship.spec.js.snap @@ -8,9 +8,9 @@ exports[`FormConfigRelationship when the fields are invalid and dirty or the for - + Forgot Password? - + @@ -87,9 +87,9 @@ exports[`Login on instance should render the component with the login form 1`] = className="btn btn-block btn-lg btn-primary" type="submit" > - + Login - +

    @@ -127,7 +127,11 @@ exports[`Login submit() on response failure when authorization conflict (2fa req className="form-group login-token" >
    - Two-step verification + + Two-step verification +
    - Authentication code + + Authentication code +

    - + Open the two-factor Authenticator app on your device - +
    - + to view your authentication code and verify your identity. - +

    - + Return to login - +

    @@ -176,9 +184,9 @@ exports[`Login submit() on response failure when authorization conflict (2fa req className="btn btn-block btn-lg btn-primary" type="submit" > - + Verify - +

    @@ -216,7 +224,11 @@ exports[`Login submit() on response failure when authorization failure should se className="form-group login-token has-error" >
    - Two-step verification + + Two-step verification +
    - Authentication code + + Authentication code + - Two-factor verification failed + + Two-factor verification failed +

    - + Open the two-factor Authenticator app on your device - +
    - + to view your authentication code and verify your identity. - +

    - + Return to login - +

    @@ -272,9 +292,9 @@ exports[`Login submit() on response failure when authorization failure should se className="btn btn-block btn-lg btn-primary" type="submit" > - + Verify - +

    diff --git a/app/react/V2/Components/Forms/Geolocation.tsx b/app/react/V2/Components/Forms/Geolocation.tsx index 6cff296396..48f99d7d93 100644 --- a/app/react/V2/Components/Forms/Geolocation.tsx +++ b/app/react/V2/Components/Forms/Geolocation.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Translate } from 'app/I18N'; import { Map, Layer } from 'app/Map/MapContainer'; -import { Label, InputField } from 'app/V2/Components/Forms'; +import { Label, InputField } from 'V2/Components/Forms'; interface GeolocationProps { name: string; diff --git a/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx b/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx index 3ecb07d0f1..3ccafd4980 100644 --- a/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx +++ b/app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx @@ -1,8 +1,6 @@ import React from 'react'; import 'cypress-axe'; -import { Provider } from 'react-redux'; import { mount } from '@cypress/react18'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { MultiselectList } from '../MultiselectList'; describe('MultiselectList.cy.tsx', () => { @@ -58,16 +56,14 @@ describe('MultiselectList.cy.tsx', () => { beforeEach(() => { cy.viewport(450, 650); mount( - -
    - { - selected = selectedItems; - }} - /> -
    -
    +
    + { + selected = selectedItems; + }} + /> +
    ); }); @@ -145,17 +141,15 @@ describe('MultiselectList.cy.tsx', () => { cy.viewport(450, 650); mount( - -
    - { - selections.push(...selectedItems); - }} - allowSelelectAll - /> -
    -
    +
    + { + selections.push(...selectedItems); + }} + allowSelelectAll + /> +
    ); cy.contains('button', 'Select all').click(); @@ -179,17 +173,15 @@ describe('MultiselectList.cy.tsx', () => { cy.viewport(450, 650); mount( - -
    - { - selections.push(...selectedItems); - }} - allowSelelectAll - /> -
    -
    +
    + { + selections.push(...selectedItems); + }} + allowSelelectAll + /> +
    ); cy.contains('button', 'Select all').click(); @@ -211,11 +203,9 @@ describe('MultiselectList.cy.tsx', () => { it('should show matching options even when not selected', () => { cy.viewport(450, 650); mount( - -
    - {}} items={pizzas} value={['MGT']} /> -
    -
    +
    + {}} items={pizzas} value={['MGT']} /> +
    ); cy.get('input[type=text]').type('pepperoni'); @@ -228,11 +218,9 @@ describe('MultiselectList.cy.tsx', () => { it('should show blank state property if there is no items passed to the component', () => { cy.viewport(450, 650); mount( - -
    - {}} items={[]} /> -
    -
    +
    + {}} items={[]} /> +
    ); cy.contains('No items available').should('be.visible'); }); @@ -240,11 +228,9 @@ describe('MultiselectList.cy.tsx', () => { it('should accept a blank state string', () => { cy.viewport(450, 650); mount( - -
    - {}} items={[]} blankState="nada" /> -
    -
    +
    + {}} items={[]} blankState="nada" /> +
    ); cy.contains('nada').should('be.visible'); }); @@ -252,15 +238,9 @@ describe('MultiselectList.cy.tsx', () => { it('should accept a blank state component', () => { cy.viewport(450, 650); mount( - -
    - {}} - items={[]} - blankState={
    no items string
    } - /> -
    -
    +
    + {}} items={[]} blankState={
    no items string
    } /> +
    ); cy.contains('no items string').should('be.visible'); }); diff --git a/app/react/V2/Components/Layouts/SettingsContent.tsx b/app/react/V2/Components/Layouts/SettingsContent.tsx index 8407bc1ff1..4c61e29905 100644 --- a/app/react/V2/Components/Layouts/SettingsContent.tsx +++ b/app/react/V2/Components/Layouts/SettingsContent.tsx @@ -2,7 +2,7 @@ import React, { PropsWithChildren } from 'react'; import { Breadcrumb } from 'flowbite-react'; import { ChevronLeftIcon } from '@heroicons/react/20/solid'; -import { I18NLink, Translate } from 'app/I18N'; +import { Translate, I18NLinkV2 as I18NLink } from 'app/I18N'; interface SettingsContentProps extends PropsWithChildren { className?: string; @@ -38,8 +38,10 @@ const SettingsHeader = ({ contextId, title, children, path, className }: Setting {Array.from(path?.entries() || []).map(([key, value]) => ( - - {key} + + + {key} + ))} {title !== undefined && ( diff --git a/app/react/V2/Components/Layouts/specs/SettingsContent.cy.tsx b/app/react/V2/Components/Layouts/specs/SettingsContent.cy.tsx index 40f4b33491..18c9ac9950 100644 --- a/app/react/V2/Components/Layouts/specs/SettingsContent.cy.tsx +++ b/app/react/V2/Components/Layouts/specs/SettingsContent.cy.tsx @@ -1,37 +1,33 @@ import React from 'react'; import 'cypress-axe'; import { BrowserRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; import { mount } from '@cypress/react18'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { SettingsContent } from '../SettingsContent'; describe('ConfirmationModal', () => { const render = () => { mount(
    - - - - - - - Body - - - Footer - - - + + + + + + Body + + + Footer + +
    ); }; @@ -47,7 +43,7 @@ describe('ConfirmationModal', () => { cy.get('[data-testid="settings-content-header"]') .invoke('text') .should('contain', 'Root PathMiddle PathLeafCurrent page'); - cy.get('a[href="/en/settings"]').should('not.be.visible'); + cy.get('a[href="/settings"]').should('not.be.visible'); cy.contains('a', 'Root Path').invoke('attr', 'href').should('include', '#top'); cy.contains('a', 'Middle Path').invoke('attr', 'href').should('include', '#bottom'); cy.contains('a', 'Leaf').invoke('attr', 'href').should('include', '#footer'); @@ -58,6 +54,6 @@ describe('ConfirmationModal', () => { it('should have an arrow to return to settings menu for mobile', () => { cy.viewport(450, 650); render(); - cy.get('a[href="/en/settings"]').should('be.visible'); + cy.get('a[href="/settings"]').should('be.visible'); }); }); diff --git a/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx b/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx index 2e6ea167ff..58f85e5763 100644 --- a/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx +++ b/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx @@ -5,9 +5,8 @@ import React from 'react'; import { render, act, queryAllByAttribute, cleanup, RenderResult } from '@testing-library/react'; import { configMocks, mockIntersectionObserver } from 'jsdom-testing-mocks'; -import { Provider } from 'react-redux'; import { pdfScaleAtom } from 'V2/atoms'; -import { TestAtomStoreProvider, LEGACY_createStore as createStore } from 'V2/testing'; +import { TestAtomStoreProvider } from 'V2/testing'; import PDF, { PDFProps } from '../PDF'; import * as helpers from '../functions/helpers'; @@ -78,11 +77,9 @@ describe('PDF', () => { const renderComponet = (scrollToPage?: PDFProps['scrollToPage']) => { renderResult = render( - - - - - + + + ); }; diff --git a/app/react/V2/Components/UI/Modal.tsx b/app/react/V2/Components/UI/Modal.tsx index 454f9e64dd..4521a00d37 100644 --- a/app/react/V2/Components/UI/Modal.tsx +++ b/app/react/V2/Components/UI/Modal.tsx @@ -6,9 +6,10 @@ type modalSizeType = 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'; interface ModalProps { children: string | React.ReactNode; size: modalSizeType; + id?: string; } -const Modal = ({ children, size }: ModalProps) => { +const Modal = ({ children, size, id }: ModalProps) => { const sizes = { sm: 'max-w-sm', md: 'max-w-md min-w-[24rem]', @@ -25,6 +26,7 @@ const Modal = ({ children, size }: ModalProps) => { data-testid="modal" role="dialog" aria-label="Modal" + id={id} >
    {children}
    diff --git a/app/react/V2/Components/UI/specs/CopyValueInput.cy.tsx b/app/react/V2/Components/UI/specs/CopyValueInput.cy.tsx index ad5c286618..e353765e06 100644 --- a/app/react/V2/Components/UI/specs/CopyValueInput.cy.tsx +++ b/app/react/V2/Components/UI/specs/CopyValueInput.cy.tsx @@ -1,17 +1,13 @@ import React from 'react'; import 'cypress-axe'; import { mount } from '@cypress/react18'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { CopyValueInput } from '../CopyValueInput'; describe('CopyValueInput', () => { const Component = () => ( - -
    - -
    -
    +
    + +
    ); before(() => { @@ -41,9 +37,8 @@ describe('CopyValueInput', () => { cy.get('input').should('have.value', 'some testing value'); cy.get('[data-testid="copy-value-button"]').focus(); cy.get('[data-testid="copy-value-button"]').realClick(); - cy.window() // + cy.window() .then(async win => win.navigator.clipboard.readText()) - .should('equal', 'some testing value'); }); }); diff --git a/app/react/V2/Components/UI/specs/NotificationsContainer.cy.tsx b/app/react/V2/Components/UI/specs/NotificationsContainer.cy.tsx index 91de3a2288..d2c5141a5d 100644 --- a/app/react/V2/Components/UI/specs/NotificationsContainer.cy.tsx +++ b/app/react/V2/Components/UI/specs/NotificationsContainer.cy.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Provider, useSetAtom } from 'jotai'; import { mount } from '@cypress/react18'; -import { Provider as ReduxProvider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { notificationAtom, notificationAtomType } from 'V2/atoms'; import { NotificationsContainer } from '../NotificationsContainer'; @@ -21,14 +19,12 @@ describe('Notifications container', () => { }; return ( - - <> - - - - + <> + + + ); }; diff --git a/app/react/V2/Routes/Settings/ActivityLog/components/FiltersSidePanel.tsx b/app/react/V2/Routes/Settings/ActivityLog/components/FiltersSidePanel.tsx index cdecc22c79..fa7e848d4f 100644 --- a/app/react/V2/Routes/Settings/ActivityLog/components/FiltersSidePanel.tsx +++ b/app/react/V2/Routes/Settings/ActivityLog/components/FiltersSidePanel.tsx @@ -6,7 +6,7 @@ import { Translate, t } from 'app/I18N'; import { InputField, DateRangePicker, MultiSelect } from 'app/V2/Components/Forms'; import { useAtomValue } from 'jotai'; import { ClientSettings } from 'app/apiResponseTypes'; -import { settingsAtom, translationsAtom } from 'app/V2/atoms'; +import { settingsAtom, localeAtom } from 'app/V2/atoms'; interface ActivityLogSearch { username: string; @@ -36,7 +36,7 @@ const methodOptions = ['CREATE', 'UPDATE', 'DELETE', 'MIGRATE', 'WARNING'].map(m const FiltersSidePanel = ({ isOpen, onClose, onSubmit, appliedFilters }: FiltersSidePanelProps) => { const { dateFormat = 'YYYY-MM-DD' } = useAtomValue(settingsAtom); - const { locale } = useAtomValue<{ locale: string }>(translationsAtom); + const locale = useAtomValue(localeAtom); const [currentFilters, setCurrentFilters] = useState(appliedFilters); useEffect(() => { diff --git a/app/react/V2/Routes/Settings/Languages/components/InstallLanguagesModal.tsx b/app/react/V2/Routes/Settings/Languages/components/InstallLanguagesModal.tsx index 7287aefb72..131c0674ee 100644 --- a/app/react/V2/Routes/Settings/Languages/components/InstallLanguagesModal.tsx +++ b/app/react/V2/Routes/Settings/Languages/components/InstallLanguagesModal.tsx @@ -22,6 +22,7 @@ const InstallLanguagesModal = ({ setShowModal, languages }: InstallLanguagesModa })); const install = async () => { + setShowModal(false); await requestAction( I18NApi.addLanguage, new RequestParams(languages.filter(l => selected.includes(l.key))), @@ -57,7 +58,6 @@ const InstallLanguagesModal = ({ setShowModal, languages }: InstallLanguagesModa Selected @@ -242,7 +242,7 @@ exports[`Settings Thesauri ThesauriList Thesaurus deletion should show the selec 2 of @@ -268,8 +268,8 @@ exports[`Settings Thesauri ThesauriList render existing thesauri should show a l data-testid="settings-content-header" > Navigate back @@ -328,7 +328,7 @@ exports[`Settings Thesauri ThesauriList render existing thesauri should show a l data-testid="flowbite-breadcrumb-item" > Thesauri @@ -360,7 +360,7 @@ exports[`Settings Thesauri ThesauriList render existing thesauri should show a l class="text-base font-semibold text-left text-gray-900 bg-white" > Thesauri @@ -381,7 +381,7 @@ exports[`Settings Thesauri ThesauriList render existing thesauri should show a l >
    `; @@ -135,10 +143,18 @@ exports[`ConnectionsList when there are no references should render a blank stat icon="sitemap" />

    - No References + + No References +

    - No References description + + No References description +

    `; diff --git a/app/react/Viewer/components/specs/__snapshots__/Paginator.spec.js.snap b/app/react/Viewer/components/specs/__snapshots__/Paginator.spec.js.snap index 91de2eb172..a33368199f 100644 --- a/app/react/Viewer/components/specs/__snapshots__/Paginator.spec.js.snap +++ b/app/react/Viewer/components/specs/__snapshots__/Paginator.spec.js.snap @@ -45,22 +45,14 @@ exports[`Paginator should render a previous button and next button based on the onClick={[Function]} to="undefined?page=4" > - - + - - Previous - - - + Previous + +
    @@ -87,22 +79,14 @@ exports[`Paginator should render a previous button and next button based on the onClick={[Function]} to="undefined?page=6" > - - + - - Next - - - + Next + + @@ -157,22 +141,14 @@ exports[`Paginator when base Url already has the query string "?" should add the onClick={[Function]} to="undefined?page=2" > - - + - - Previous - - - + Previous + + @@ -199,22 +175,14 @@ exports[`Paginator when base Url already has the query string "?" should add the onClick={[Function]} to="undefined?page=4" > - - + - - Next - - - + Next + + @@ -272,22 +240,14 @@ exports[`Paginator when on first page should disable the prev link 1`] = ` rel="nofollow" to="undefined?page=1" > - - + - - Previous - - - + Previous + + @@ -314,22 +274,14 @@ exports[`Paginator when on first page should disable the prev link 1`] = ` onClick={[Function]} to="undefined?page=2" > - - + - - Next - - - + Next + + @@ -384,22 +336,14 @@ exports[`Paginator when on last page should disable the next link 1`] = ` onClick={[Function]} to="undefined?page=24" > - - + - - Previous - - - + Previous + + @@ -429,22 +373,14 @@ exports[`Paginator when on last page should disable the next link 1`] = ` rel="nofollow" to="undefined?page=25" > - - + - - Next - - - + Next + + diff --git a/app/react/entry-server.tsx b/app/react/entry-server.tsx index 4780605e83..f7eba356a7 100644 --- a/app/react/entry-server.tsx +++ b/app/react/entry-server.tsx @@ -24,7 +24,7 @@ import Root from './App/Root'; import RouteHandler from './App/RouteHandler'; import { ErrorBoundary } from './V2/Components/ErrorHandling'; import { atomStore, hydrateAtomStore } from './V2/atoms'; -import { I18NUtils, t, Translate } from './I18N'; +import { I18NUtils } from './I18N'; import { IStore } from './istore'; import { getRoutes } from './Routes'; import createReduxStore from './store'; @@ -154,10 +154,10 @@ const prepareStores = async (req: ExpressRequest, settings: ClientSettings, lang const reduxData = { user: userApiResponse.json, - translations: translationsApiResponse.json.rows, templates: templatesApiResponse.json.rows, thesauris: thesaurisApiResponse.json.rows, relationTypes: relationTypesApiResponse.json.rows, + translations: translationsApiResponse.json.rows, settings: { collection: { ...settingsApiResponse.json, links: settingsApiResponse.json.links || [] }, }, @@ -176,6 +176,7 @@ const prepareStores = async (req: ExpressRequest, settings: ClientSettings, lang thesauri: thesaurisApiResponse.json.rows, templates: templatesApiResponse.json.rows, user: userApiResponse.json, + translations: translationsApiResponse.json.rows, }, }; }; @@ -268,11 +269,6 @@ const getSSRProperties = async ( }; }; -const resetTranslations = () => { - t.resetCachedTranslation(); - Translate.resetCachedTranslation(); -}; - const EntryServer = async (req: ExpressRequest, res: Response) => { RouteHandler.renderedFromServer = true; const [settings, assets] = await Promise.all([ @@ -303,7 +299,7 @@ const EntryServer = async (req: ExpressRequest, res: Response) => { reduxState, matched ); - resetTranslations(); + hydrateAtomStore(atomStoreData); const componentHtml = ReactDOMServer.renderToString( diff --git a/app/react/stories/Buttons/EmbededButton.stories.tsx b/app/react/stories/Buttons/EmbededButton.stories.tsx index f10dd2018f..0ffc3ddc75 100644 --- a/app/react/stories/Buttons/EmbededButton.stories.tsx +++ b/app/react/stories/Buttons/EmbededButton.stories.tsx @@ -1,9 +1,7 @@ import React from 'react'; -import { Provider } from 'react-redux'; import { CheckCircleIcon } from '@heroicons/react/24/outline'; import type { Meta, StoryObj } from '@storybook/react'; -import { EmbededButton } from 'app/V2/Components/UI/EmbededButton'; -import { LEGACY_createStore as createStore } from 'V2/testing'; +import { EmbededButton } from 'V2/Components/UI/EmbededButton'; import { Translate } from 'app/I18N'; const meta: Meta = { @@ -15,18 +13,16 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    - - {args.children} - -
    -
    +
    + + {args.children} + +
    ), }; diff --git a/app/react/stories/CodeEditor.stories.tsx b/app/react/stories/CodeEditor.stories.tsx index b0ba61c698..90fee45c70 100644 --- a/app/react/stories/CodeEditor.stories.tsx +++ b/app/react/stories/CodeEditor.stories.tsx @@ -1,8 +1,6 @@ import React, { useRef, useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { Provider } from 'react-redux'; import { CodeEditor, CodeEditorProps, CodeEditorInstance } from 'V2/Components/CodeEditor'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const sampleJS = `const myButton = document.getElementById('myButton'); myButton.addEventListener('click', function () { @@ -85,30 +83,28 @@ const Component = ({ language, intialValue, fallbackElement }: CodeEditorProps) const [updatedCode, setUpdatedCode] = useState(); return ( - -
    -
    - { - editorInstance.current = editor; - }} - fallbackElement={fallbackElement} - /> -
    -
    - -
    -
    {updatedCode}
    +
    +
    + { + editorInstance.current = editor; + }} + fallbackElement={fallbackElement} + />
    - +
    + +
    +
    {updatedCode}
    +
    ); }; diff --git a/app/react/stories/ConfirmationModal.stories.tsx b/app/react/stories/ConfirmationModal.stories.tsx index 3a545ba018..26c1cee8bb 100644 --- a/app/react/stories/ConfirmationModal.stories.tsx +++ b/app/react/stories/ConfirmationModal.stories.tsx @@ -2,9 +2,7 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; import { action } from '@storybook/addon-actions'; -import { Provider } from 'react-redux'; import { ConfirmationModal } from 'app/V2/Components/UI/ConfirmationModal'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { Translate } from 'app/I18N'; const meta: Meta = { @@ -22,24 +20,22 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -
    - -
    +
    +
    +
    - +
    ), }; diff --git a/app/react/stories/ErrorBoundary.stories.tsx b/app/react/stories/ErrorBoundary.stories.tsx index 1275fb5fe0..22182ee724 100644 --- a/app/react/stories/ErrorBoundary.stories.tsx +++ b/app/react/stories/ErrorBoundary.stories.tsx @@ -1,7 +1,5 @@ import React, { ComponentClass } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { ErrorBoundary } from 'app/V2/Components/ErrorHandling'; import type { ErrorBoundaryProps } from 'app/V2/Components/ErrorHandling'; @@ -14,11 +12,9 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    - {args.children} -
    -
    +
    + {args.children} +
    ), }; diff --git a/app/react/stories/Forms/Checkbox.stories.tsx b/app/react/stories/Forms/Checkbox.stories.tsx index 0865a6099f..6d454e218f 100644 --- a/app/react/stories/Forms/Checkbox.stories.tsx +++ b/app/react/stories/Forms/Checkbox.stories.tsx @@ -3,8 +3,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { fn } from '@storybook/test'; import { Checkbox } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; -import { Provider } from 'react-redux'; const meta: Meta = { title: 'Forms/Checkbox', @@ -18,17 +16,15 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - - - + ), }; diff --git a/app/react/stories/Forms/ColorPicker.stories.tsx b/app/react/stories/Forms/ColorPicker.stories.tsx index 197496d078..8ec3da957c 100644 --- a/app/react/stories/Forms/ColorPicker.stories.tsx +++ b/app/react/stories/Forms/ColorPicker.stories.tsx @@ -1,10 +1,8 @@ import React from 'react'; -import { Provider as ReduxProvider } from 'react-redux'; import { Meta, StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; import { action } from '@storybook/addon-actions'; import { ColorPicker } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const meta: Meta = { title: 'Forms/ColorPicker', @@ -23,16 +21,14 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    - -
    -
    +
    + +
    ), }; diff --git a/app/react/stories/Forms/DatePicker.stories.tsx b/app/react/stories/Forms/DatePicker.stories.tsx index 32cb6c873a..4c7b1dcc1b 100644 --- a/app/react/stories/Forms/DatePicker.stories.tsx +++ b/app/react/stories/Forms/DatePicker.stories.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import { Provider as ReduxProvider } from 'react-redux'; import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { fn } from '@storybook/test'; -import { DatePicker } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore, TestAtomStoreProvider } from 'V2/testing'; +import { DatePicker } from 'V2/Components/Forms'; +import { TestAtomStoreProvider } from 'V2/testing'; import { settingsAtom } from 'V2/atoms'; const meta: Meta = { @@ -26,24 +25,22 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - - - - - + + + ), }; diff --git a/app/react/stories/Forms/DateRangePicker.stories.tsx b/app/react/stories/Forms/DateRangePicker.stories.tsx index d502972c38..7dbdbd1ea3 100644 --- a/app/react/stories/Forms/DateRangePicker.stories.tsx +++ b/app/react/stories/Forms/DateRangePicker.stories.tsx @@ -1,10 +1,8 @@ import React from 'react'; -import { Provider } from 'react-redux'; import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { fn } from '@storybook/test'; import { DateRangePicker } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const meta: Meta = { title: 'Forms/DateRangePicker', @@ -24,23 +22,21 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -
    - -
    +
    +
    +
    - +
    ), }; diff --git a/app/react/stories/Forms/EnableButtonCheckbox.stories.tsx b/app/react/stories/Forms/EnableButtonCheckbox.stories.tsx index 940a502932..5f79b6b5d0 100644 --- a/app/react/stories/Forms/EnableButtonCheckbox.stories.tsx +++ b/app/react/stories/Forms/EnableButtonCheckbox.stories.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { EnableButtonCheckbox } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; -import { Provider } from 'react-redux'; const meta: Meta = { title: 'Forms/EnableButtonCheckbox', @@ -14,14 +12,12 @@ type Story = StoryObj; const Primary: Story = { render: args => (
    - - - +
    ), }; diff --git a/app/react/stories/Forms/FileDropzone.stories.tsx b/app/react/stories/Forms/FileDropzone.stories.tsx index 149229e9e0..ca390cc4c5 100644 --- a/app/react/stories/Forms/FileDropzone.stories.tsx +++ b/app/react/stories/Forms/FileDropzone.stories.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { FileDropzone } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; -import { Provider } from 'react-redux'; const meta: Meta = { title: 'Forms/FileDropzone', @@ -13,11 +11,9 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    - -
    -
    +
    + +
    ), }; diff --git a/app/react/stories/Forms/InputField.stories.tsx b/app/react/stories/Forms/InputField.stories.tsx index 6f756da7e2..5b54f7a511 100644 --- a/app/react/stories/Forms/InputField.stories.tsx +++ b/app/react/stories/Forms/InputField.stories.tsx @@ -1,8 +1,6 @@ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { InputField } from 'V2/Components/Forms'; const meta: Meta = { @@ -14,13 +12,11 @@ type Story = StoryObj; const InputFieldStory: Story = { render: args => ( - -
    -
    - -
    +
    +
    +
    - +
    ), }; diff --git a/app/react/stories/Forms/MultiSelect.stories.tsx b/app/react/stories/Forms/MultiSelect.stories.tsx index 6b0fadf717..f1ef8c122c 100644 --- a/app/react/stories/Forms/MultiSelect.stories.tsx +++ b/app/react/stories/Forms/MultiSelect.stories.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; -import { Provider } from 'react-redux'; import { MultiSelect } from 'V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const meta: Meta = { title: 'Forms/MultiSelect', @@ -13,23 +11,21 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -
    -

    Multiselect component

    - -
    +
    +
    +

    Multiselect component

    +
    - +
    ), }; diff --git a/app/react/stories/Forms/MultiselectList.stories.tsx b/app/react/stories/Forms/MultiselectList.stories.tsx index c61c6d7374..1f4d396f83 100644 --- a/app/react/stories/Forms/MultiselectList.stories.tsx +++ b/app/react/stories/Forms/MultiselectList.stories.tsx @@ -1,8 +1,6 @@ import React, { useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; -import { Provider } from 'react-redux'; import { MultiselectList } from 'V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const meta: Meta = { title: 'Forms/MultiselectList', @@ -15,32 +13,30 @@ const StoryComponent = ({ args }: any) => { const [searchAndFocus, setSearchAndFocus] = useState(''); return ( - - <> -
    -
    - -
    + <> +
    +
    +
    - - - - +
    + + + ); }; diff --git a/app/react/stories/Forms/RadioSelect.stories.tsx b/app/react/stories/Forms/RadioSelect.stories.tsx index f0449a6c83..2d29fe2d32 100644 --- a/app/react/stories/Forms/RadioSelect.stories.tsx +++ b/app/react/stories/Forms/RadioSelect.stories.tsx @@ -3,8 +3,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { fn } from '@storybook/test'; import { RadioSelect } from 'app/V2/Components/Forms'; -import { LEGACY_createStore as createStore } from 'V2/testing'; -import { Provider } from 'react-redux'; const meta: Meta = { title: 'Forms/RadioSelect', @@ -18,17 +16,15 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    - -
    -
    +
    + +
    ), }; diff --git a/app/react/stories/MediaPlayer.stories.tsx b/app/react/stories/MediaPlayer.stories.tsx index 92587ffd53..a663c482da 100644 --- a/app/react/stories/MediaPlayer.stories.tsx +++ b/app/react/stories/MediaPlayer.stories.tsx @@ -1,7 +1,5 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { MediaPlayer } from 'V2/Components/UI'; const meta: Meta = { @@ -13,18 +11,16 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -
    - -
    +
    +
    +
    - +
    ), }; diff --git a/app/react/stories/Notification.stories.tsx b/app/react/stories/Notification.stories.tsx index 64a10a6bbd..d913bdb31a 100644 --- a/app/react/stories/Notification.stories.tsx +++ b/app/react/stories/Notification.stories.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { Notification } from 'V2/Components/UI/Notification'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const meta: Meta = { title: 'Components/Notification', @@ -13,18 +11,16 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -
    - -
    +
    +
    +
    - +
    ), }; diff --git a/app/react/stories/PDF.stories.tsx b/app/react/stories/PDF.stories.tsx index 6670cccfe8..8f08152bd3 100644 --- a/app/react/stories/PDF.stories.tsx +++ b/app/react/stories/PDF.stories.tsx @@ -1,8 +1,6 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { PDF } from 'V2/Components/PDFViewer'; import { highlights } from './fixtures/PDFStoryFixtures'; @@ -16,22 +14,20 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -

    PDF Container:

    -
    - -
    -

    End of container

    +
    +

    PDF Container:

    +
    +
    - +

    End of container

    +
    ), }; diff --git a/app/react/stories/Paginator.stories.tsx b/app/react/stories/Paginator.stories.tsx index 9664ce3461..42b45c6e8b 100644 --- a/app/react/stories/Paginator.stories.tsx +++ b/app/react/stories/Paginator.stories.tsx @@ -1,9 +1,7 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; import type { Meta, StoryObj } from '@storybook/react'; import { Paginator } from 'app/V2/Components/UI'; -import { LEGACY_createStore as createStore } from 'V2/testing'; const meta: Meta = { title: 'Components/Paginator', @@ -15,18 +13,16 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - -
    -
    - -
    +
    +
    +
    - +
    ), }; diff --git a/app/react/stories/Sidepanel.stories.tsx b/app/react/stories/Sidepanel.stories.tsx index 2aaa56b1bd..c5f689fa82 100644 --- a/app/react/stories/Sidepanel.stories.tsx +++ b/app/react/stories/Sidepanel.stories.tsx @@ -1,8 +1,6 @@ import React, { useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; -import { Provider } from 'react-redux'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { Sidepanel, Button } from 'V2/Components/UI'; import { SidePanelProps } from 'app/V2/Components/UI/Sidepanel'; import { GeneratedContent } from './helpers/GeneratedContent'; @@ -16,70 +14,65 @@ const SidePanelContainer = (args: SidePanelProps) => { const [showSidepanel, setShowSidepanel] = useState(false); return ( - -
    -
    -
    -

    This a content title

    +
    +
    +
    +

    This a content title

    -

    - Lorem ipsum dolor sit amet consectetur adipisicing elit. -

    +

    Lorem ipsum dolor sit amet consectetur adipisicing elit.

    -

    - Fusce id mi eu mauris bibendum dignissim nec in sem. Sed ultrices varius mauris quis - placerat. Donec imperdiet sodales diam sed imperdiet. Aenean a nisl venenatis lectus - mattis pellentesque. Duis fermentum ante a ultricies feugiat. Proin dapibus luctus - purus id viverra. Aenean a aliquet nibh. Aenean facilisis justo quis sem auctor, nec - mollis tortor placerat. Cras eget enim mollis, mollis risus gravida, pharetra risus. - Mauris dapibus malesuada mi, quis ornare felis imperdiet eget. Donec sed quam non - dolor sodales hendrerit. Aenean suscipit, velit sed laoreet cursus, ante odio - tristique lectus, a porta eros felis eu sem. Curabitur eu gravida dolor. Ut iaculis - lacus vitae libero viverra interdum. Phasellus ac est consectetur, malesuada nisl nec, - blandit lorem. -

    +

    + Fusce id mi eu mauris bibendum dignissim nec in sem. Sed ultrices varius mauris quis + placerat. Donec imperdiet sodales diam sed imperdiet. Aenean a nisl venenatis lectus + mattis pellentesque. Duis fermentum ante a ultricies feugiat. Proin dapibus luctus purus + id viverra. Aenean a aliquet nibh. Aenean facilisis justo quis sem auctor, nec mollis + tortor placerat. Cras eget enim mollis, mollis risus gravida, pharetra risus. Mauris + dapibus malesuada mi, quis ornare felis imperdiet eget. Donec sed quam non dolor sodales + hendrerit. Aenean suscipit, velit sed laoreet cursus, ante odio tristique lectus, a + porta eros felis eu sem. Curabitur eu gravida dolor. Ut iaculis lacus vitae libero + viverra interdum. Phasellus ac est consectetur, malesuada nisl nec, blandit lorem. +

    -

    - Fusce id mi eu mauris bibendum dignissim nec in sem. Sed ultrices varius mauris quis - placerat. Donec imperdiet sodales diam sed imperdiet. Aenean a nisl venenatis lectus - mattis pellentesque. Duis fermentum ante a ultricies feugiat.  - - Proin dapibus luctus purus id viverra. - -  Aenean a aliquet nibh. Aenean facilisis justo quis sem auctor, nec mollis tortor - placerat. Cras eget enim mollis, mollis risus gravida, pharetra risus. Mauris dapibus - malesuada mi, quis ornare felis imperdiet eget. Donec sed quam non dolor sodales - hendrerit. Aenean suscipit, velit sed laoreet cursus, ante odio tristique lectus, a - porta eros felis eu sem. Curabitur eu gravida dolor. Ut iaculis lacus vitae libero - viverra interdum. Phasellus ac est consectetur, malesuada nisl nec, blandit lorem. -

    +

    + Fusce id mi eu mauris bibendum dignissim nec in sem. Sed ultrices varius mauris quis + placerat. Donec imperdiet sodales diam sed imperdiet. Aenean a nisl venenatis lectus + mattis pellentesque. Duis fermentum ante a ultricies feugiat.  + + Proin dapibus luctus purus id viverra. + +  Aenean a aliquet nibh. Aenean facilisis justo quis sem auctor, nec mollis tortor + placerat. Cras eget enim mollis, mollis risus gravida, pharetra risus. Mauris dapibus + malesuada mi, quis ornare felis imperdiet eget. Donec sed quam non dolor sodales + hendrerit. Aenean suscipit, velit sed laoreet cursus, ante odio tristique lectus, a + porta eros felis eu sem. Curabitur eu gravida dolor. Ut iaculis lacus vitae libero + viverra interdum. Phasellus ac est consectetur, malesuada nisl nec, blandit lorem. +

    -
    +
    - -
    - setShowSidepanel(false)} - size={args.size} - > - - - - - - - -
    + +
    + setShowSidepanel(false)} + size={args.size} + > + + + + + + +
    - +
    ); }; diff --git a/app/react/stories/Table.stories.tsx b/app/react/stories/Table.stories.tsx index 3fad235cf6..91077cbc5e 100644 --- a/app/react/stories/Table.stories.tsx +++ b/app/react/stories/Table.stories.tsx @@ -3,9 +3,7 @@ import React, { useRef, useState } from 'react'; import { Meta, StoryObj } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { Cell, createColumnHelper, SortingState } from '@tanstack/react-table'; -import { Provider } from 'react-redux'; import { Button, Table } from 'V2/Components/UI'; -import { LEGACY_createStore as createStore } from 'V2/testing'; import { BasicData, DataWithGroups, basicData, dataWithGroups } from './table/fixtures'; type StoryProps = { @@ -225,17 +223,15 @@ type Story = StoryObj; const Primary: Story = { render: args => ( - - - + ), }; diff --git a/app/react/utils/useOnClickOutsideElementHook.ts b/app/react/utils/useOnClickOutsideElementHook.ts index 050e479b3f..0640322475 100644 --- a/app/react/utils/useOnClickOutsideElementHook.ts +++ b/app/react/utils/useOnClickOutsideElementHook.ts @@ -13,7 +13,7 @@ export function useOnClickOutsideElement( cb(event); }; - document.addEventListener('click', onClickHandler); + document.addEventListener('click', onClickHandler, { capture: true }); return () => { document.removeEventListener('click', onClickHandler); }; diff --git a/app/shared/translate.js b/app/shared/translate.js index 0cd87d33ac..d52c4d828f 100644 --- a/app/shared/translate.js +++ b/app/shared/translate.js @@ -1,10 +1,8 @@ -/** @format */ - -export function getLocaleTranslation(translations, locale) { +function getLocaleTranslation(translations, locale) { return translations.find(d => d.locale === locale) || { contexts: [] }; } -export function getContext(translation, contextId = '') { +function getContext(translation, contextId = '') { return ( translation.contexts.find(ctx => ctx.id.toString() === contextId.toString()) || { values: {} } ); @@ -13,3 +11,5 @@ export function getContext(translation, contextId = '') { export default function translate(context, key, text) { return context.values[key] || text; } + +export { getLocaleTranslation, getContext }; diff --git a/cypress/e2e/__image_snapshots__/PDF display responsiveness IX sidepanel should open the pdf sidepanel and show in the correct page #0.png b/cypress/e2e/__image_snapshots__/PDF display responsiveness IX sidepanel should open the pdf sidepanel and show in the correct page #0.png deleted file mode 100644 index 981fe7e34c2fa2a79988b75d9b37ea667e18f322..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104058 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�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 diff --git a/cypress/e2e/__image_snapshots__/PDF display responsiveness IX sidepanel should open the pdf sidepanel and show the correct page #0.png b/cypress/e2e/__image_snapshots__/PDF display responsiveness IX sidepanel should open the pdf sidepanel and show the correct page #0.png new file mode 100644 index 0000000000000000000000000000000000000000..d63b45bea5a70debb5738a788b56b9c0a0409a16 GIT binary patch literal 103827 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ⅅ4@^HYfccJDL%f9wt#Ra4n#LU>p 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 literal 0 HcmV?d00001 diff --git a/cypress/e2e/pdf-display.cy.ts b/cypress/e2e/pdf-display.cy.ts index 999821840b..afbb5ddde4 100644 --- a/cypress/e2e/pdf-display.cy.ts +++ b/cypress/e2e/pdf-display.cy.ts @@ -250,7 +250,8 @@ describe('PDF display', () => { cy.contains('td', 'Entity with pdf (es)'); }); - it('should open the pdf sidepanel and show in the correct page', () => { + it('should open the pdf sidepanel and show the correct page', () => { + cy.contains('button', 'Open PDF').scrollIntoView(); cy.contains('button', 'Open PDF').realTouch(); cy.contains('Loading').should('not.exist'); cy.get('#pdf-container').within(() => { @@ -262,23 +263,21 @@ describe('PDF display', () => { cy.get('#root').toMatchImageSnapshot(); }); - it('should only show visible pages', () => { - cy.get('#pdf-container').within(() => { - cy.get('#page-1-container .page').should('be.empty'); - cy.get('#page-2-container .page').should('not.be.empty'); - cy.get('#page-3-container .page').should('not.be.empty'); - cy.get('#page-10-container .page').should('be.empty'); - cy.get('#page-10-container').scrollIntoView(); - cy.get('#page-1-container .page').should('be.empty'); - cy.get('#page-2-container .page').should('be.empty'); - cy.get('#page-3-container .page').should('be.empty'); - cy.get('#page-10-container .page').should('not.be.empty'); - cy.get('#page-11-container .page').should('not.be.empty'); - cy.contains( - 'span[role="presentation"]', - 'El artículo 63.2 de la Convención exige que para que la Corte pueda disponer de' - ).should('be.visible'); - }); + it('should check page rendering', () => { + cy.get('#page-1-container .page').should('be.empty'); + cy.get('#page-2-container .page').should('not.be.empty'); + cy.get('#page-3-container .page').should('not.be.empty'); + cy.get('#page-10-container .page').should('be.empty'); + cy.get('#page-10-container').scrollIntoView(); + cy.get('#page-1-container .page').should('be.empty'); + cy.get('#page-2-container .page').should('be.empty'); + cy.get('#page-3-container .page').should('be.empty'); + cy.get('#page-10-container .page').should('not.be.empty'); + cy.get('#page-11-container .page').should('not.be.empty'); + cy.contains( + 'span[role="presentation"]', + 'El artículo 63.2 de la Convención exige que para que la Corte pueda disponer de' + ).should('be.visible'); }); }); }); diff --git a/cypress/e2e/settings/__snapshots__/activitylog.cy.ts.snap b/cypress/e2e/settings/__snapshots__/activitylog.cy.ts.snap index cee99896ce..b2bd629931 100644 --- a/cypress/e2e/settings/__snapshots__/activitylog.cy.ts.snap +++ b/cypress/e2e/settings/__snapshots__/activitylog.cy.ts.snap @@ -2,7 +2,7 @@ exports[`Activity log > should list the last activity log entries #0`] = ` UPDATEUPDATE `; @@ -14,7 +14,7 @@ exports[`Activity log > should list the last activity log entries #2`] = `

    Updated user:editor (58ada34d299e82674854510e)
    @@ -25,7 +25,7 @@ exports[`Activity log > should list the last activity log entries #3`] = ` CREATECREATE `; @@ -44,78 +44,6 @@ exports[`Activity log > should list the last activity log entries #5`] = ` `; exports[`Activity log > should open the detail of an entry #0`] = ` - -`; - -exports[`Activity log > should open the detail of an entry #1`] = `
    - editor (58ada34d299e82674854510e) + editor (58ada34d299e82674854510e)
    UPDATEUPDATE
    @@ -178,7 +108,7 @@ exports[`Activity log > should open the detail of an entry #1`] = ` >Body{"_id":"58ada34d299e82674854510e","username":"editor","__v":0,"role":"editor","groups":[{"_id":"5fda2b675917fe58048a88f4","name":"Activistas"},{"_id":"5fda2b6f5917fe58048a88f6","name":"Asesores + >{"_id":"58ada34d299e82674854510e","username":"editor","__v":1,"role":"editor","groups":[{"_id":"5fda2b675917fe58048a88f4","name":"Activistas"},{"_id":"5fda2b6f5917fe58048a88f6","name":"Asesores legales"}],"email":"editor@uwazi.com"}
    diff --git a/cypress/e2e/settings/languages.cy.ts b/cypress/e2e/settings/languages.cy.ts index 91c43de4ea..a62e2429de 100644 --- a/cypress/e2e/settings/languages.cy.ts +++ b/cypress/e2e/settings/languages.cy.ts @@ -1,15 +1,29 @@ import { clearCookiesAndLogin } from '../helpers/login'; import 'cypress-axe'; -const addLanguages = (languages: string[]) => { - languages.forEach(lang => { - cy.clearAndType('[data-testid=modal] input[type=text]', lang); - cy.contains('button', lang).click(); +const stringToTranslate = "*please keep this key secret and don't share it."; + +const addLanguages = () => { + cy.contains('Install Language').click(); + cy.get('[data-testid=modal]') + .should('be.visible') + .within(() => { + cy.get('input[type=text]').realClick().realType('Spanish'); + cy.contains('button', 'Spanish').should('be.visible').realClick(); + cy.get('input[type=text]').clear(); + cy.get('input[type=text]').realType('French'); + cy.contains('button', 'French').should('be.visible').realClick(); + cy.get('input[type=text]').clear(); + cy.contains('label', '(2)').click(); + cy.contains('span', '* French (fr)').should('be.visible'); + cy.contains('span', '* Spanish (es)').should('be.visible'); + }); + cy.get('[data-testid=modal]').within(() => { + cy.contains('button', 'Install (2)').realClick(); }); + cy.get('[data-testid=modal]').should('not.exist'); }; -const stringToTranslate = "*please keep this key secret and don't share it."; - describe('Languages', () => { before(() => { cy.blankState(); @@ -22,26 +36,31 @@ describe('Languages', () => { describe('Languages List', () => { it('should open the install language modal', () => { cy.contains('Install Language').click(); + cy.get('[data-testid=modal]').should('be.visible'); cy.checkA11y(); + cy.get('[data-testid=modal]').within(() => { + cy.contains('button', 'Cancel').click(); + }); }); it('should install new languages', () => { const BACKEND_LANGUAGE_INSTALL_DELAY = 25000; cy.intercept('POST', 'api/translations/languages').as('addLanguage'); - addLanguages(['Spanish', 'French']); - cy.contains('[data-testid=modal] button', 'Install').click(); + + addLanguages(); + cy.wait('@addLanguage'); cy.contains('Dismiss').click(); - cy.contains('Spanish', { timeout: BACKEND_LANGUAGE_INSTALL_DELAY }); - cy.contains('French', { timeout: BACKEND_LANGUAGE_INSTALL_DELAY }); + cy.contains('tr', 'Spanish', { timeout: BACKEND_LANGUAGE_INSTALL_DELAY }); + cy.contains('tr', 'French', { timeout: BACKEND_LANGUAGE_INSTALL_DELAY }); cy.contains('Languages installed successfully').click(); }); it('should render the list of installed languages', () => { cy.get('[data-testid=settings-languages]').toMatchImageSnapshot(); - cy.contains('English'); - cy.contains('Spanish'); - cy.contains('French'); + cy.contains('tr', 'English'); + cy.contains('tr', 'Spanish'); + cy.contains('tr', 'French'); cy.checkA11y(); }); }); diff --git a/cypress/e2e/settings/translations.cy.ts b/cypress/e2e/settings/translations.cy.ts index d32c6af17f..9458c24f75 100644 --- a/cypress/e2e/settings/translations.cy.ts +++ b/cypress/e2e/settings/translations.cy.ts @@ -52,7 +52,7 @@ describe('Translations', () => { }); it('should have breadcrumb navigation', () => { - cy.contains('li > a > .translation', 'Translations').click(); + cy.contains('li a > .translation', 'Translations').click(); cy.contains('caption', 'System translations'); }); @@ -145,12 +145,14 @@ describe('Translations', () => { it('should translate a text', () => { cy.contains('span', 'Filters').click(); - cy.get('input[id=es]').clear(); - cy.get('input[id=es]').type('Filtros', { delay: 0 }); - cy.get('input[id=en]').clear(); - cy.get('input[id=en]').type('Filtering', { delay: 0 }); - cy.contains('button', 'Submit').click(); - cy.get('[data-testid=modal]').should('not.exist'); + cy.get('#translationsFormModal').within(() => { + cy.get('input[name="data.1.value"]').clear(); + cy.get('input[name="data.1.value"]').type('Filtros', { delay: 0 }); + cy.get('input[name="data.0.value"]').clear(); + cy.get('input[name="data.0.value"]').type('Filtering', { delay: 0 }); + cy.contains('button', 'Save').click(); + }); + cy.get('#translationsFormModal').should('not.exist'); }); it('should deactive the live translate and check the translatations in english and spanish', () => { diff --git a/tailwind.config.js b/tailwind.config.js index 028918c9b4..6aeeb98522 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -3,7 +3,7 @@ const colors = require('tailwindcss/colors'); module.exports = { content: [ - './app/react/V2/**/*.{js,jsx,ts,tsx}', + './app/react/**/*.{js,jsx,ts,tsx}', './app/react/stories/**/*.{js,jsx,ts,tsx}', 'node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}', 'node_modules/flowbite-datepicker/**/*.{js,jsx,ts,tsx,css}',