diff --git a/.github/workflows/ci_cypress_component.yml b/.github/workflows/ci_cypress_component.yml index a50dca1f15..71c8248648 100644 --- a/.github/workflows/ci_cypress_component.yml +++ b/.github/workflows/ci_cypress_component.yml @@ -13,6 +13,8 @@ concurrency: jobs: cypress-run: runs-on: ubuntu-20.04 + env: + CYPRESS_RETRIES: 2 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/ci_e2e_cypress_base.yml b/.github/workflows/ci_e2e_cypress_base.yml index 8fe217e586..2018c42595 100644 --- a/.github/workflows/ci_e2e_cypress_base.yml +++ b/.github/workflows/ci_e2e_cypress_base.yml @@ -13,6 +13,8 @@ concurrency: jobs: e2e-cypress: runs-on: ubuntu-20.04 + env: + CYPRESS_RETRIES: 2 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.7-amd64 diff --git a/.github/workflows/ci_e2e_cypress_pages.yml b/.github/workflows/ci_e2e_cypress_pages.yml index c8c38dc5dc..9710d3bc61 100644 --- a/.github/workflows/ci_e2e_cypress_pages.yml +++ b/.github/workflows/ci_e2e_cypress_pages.yml @@ -13,6 +13,8 @@ concurrency: jobs: e2e-cypress: runs-on: ubuntu-20.04 + env: + CYPRESS_RETRIES: 2 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.7-amd64 diff --git a/.github/workflows/ci_e2e_cypress_ssettings.yml b/.github/workflows/ci_e2e_cypress_ssettings.yml index 74385df465..801adfec47 100644 --- a/.github/workflows/ci_e2e_cypress_ssettings.yml +++ b/.github/workflows/ci_e2e_cypress_ssettings.yml @@ -13,6 +13,8 @@ concurrency: jobs: e2e-cypress: runs-on: ubuntu-20.04 + env: + CYPRESS_RETRIES: 2 services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.7-amd64 diff --git a/app/react/App/scss/modules/_metadata-view.scss b/app/react/App/scss/modules/_metadata-view.scss index 9533ed67de..85b65066fb 100644 --- a/app/react/App/scss/modules/_metadata-view.scss +++ b/app/react/App/scss/modules/_metadata-view.scss @@ -174,7 +174,8 @@ ul.comma-separated li { @mixin media-error { padding: 10px; - border: 1px #ff775c solid; + border: 1px #d43f3a solid; + color: #444444; } dl.metadata-type-multimedia { @@ -184,7 +185,9 @@ dl.metadata-type-multimedia { } img { - box-shadow: 3px 1px 6px 0px rgba(0, 0, 0, 0.2), -3px 1px 6px 0px rgba(0, 0, 0, 0.2); + box-shadow: + 3px 1px 6px 0px rgba(0, 0, 0, 0.2), + -3px 1px 6px 0px rgba(0, 0, 0, 0.2); &.contain { max-width: calc(100% - 10px); diff --git a/app/react/Forms/components/MediaField.tsx b/app/react/Forms/components/MediaField.tsx index c8a69011c0..1d02493d4c 100644 --- a/app/react/Forms/components/MediaField.tsx +++ b/app/react/Forms/components/MediaField.tsx @@ -6,6 +6,7 @@ import { ClientFile } from 'app/istore'; import { prepareHTMLMediaView } from 'shared/fileUploadUtils'; import { MediaModal, MediaModalProps, MediaModalType } from 'app/Metadata/components/MediaModal'; import MarkdownMedia, { TimeLink } from 'app/Markdown/components/MarkdownMedia'; +import { ImageViewer } from 'app/Metadata/components/ImageViewer'; type MediaFieldProps = MediaModalProps & { value: string | { data: string; originalFile: Partial } | null; @@ -61,11 +62,6 @@ const MediaField = (props: MediaFieldProps) => { multipleEdition, } = props; const [openModal, setOpenModal] = useState(false); - const [imageRenderError, setImageRenderError] = useState(false); - - useEffect(() => { - setImageRenderError(false); - }, [localAttachments]); const handleCloseMediaModal = () => { setOpenModal(false); @@ -122,13 +118,6 @@ const MediaField = (props: MediaFieldProps) => { {(() => { - if (imageRenderError) { - return ( -
- This file type is not supported on media fields -
- ); - } if ( (file && file.data && @@ -136,21 +125,9 @@ const MediaField = (props: MediaFieldProps) => { file.supportingFile.mimetype?.search(/image\/*/) !== -1) || type === MediaModalType.Image ) { - return file?.fileURL ? ( - { - if (file?.fileURL) { - setImageRenderError(true); - } - }} - /> - ) : ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - ); + return file?.fileURL ? : null; } + if (file?.fileURL) { return ( { /> ); } + return null; })()} { name: '', }; - const render = (otherProps = {}) => { - ({ renderResult } = renderConnectedContainer( - , - () => defaultState - )); + const render = async (otherProps = {}) => { + await act(async () => { + ({ renderResult } = renderConnectedContainer( + , + () => defaultState + )); + }); }; describe('Object URL handling', () => { const mockedCreateObjectURL: jest.Mock = jest.fn(); const mockedRevokeObjectURL: jest.Mock = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + global.fetch = jest.fn(); + (global.fetch as jest.Mock).mockResolvedValue({ ok: true }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + beforeAll(() => { URL.createObjectURL = mockedCreateObjectURL; mockedCreateObjectURL.mockReturnValue('blob:abc'); @@ -96,20 +108,21 @@ describe('MediaField', () => { it('should create an object URL with the file', async () => { const file = new File(['hello'], 'hello.png', { type: 'image/png' }); - render(imageProps); - const img = screen.getByRole('presentation') as HTMLImageElement; + await render(imageProps); + + const img = screen.getByRole('img') as HTMLImageElement; expect(img.src).toEqual('blob:abc'); expect(mockedCreateObjectURL).toHaveBeenCalledWith(file); }); it('should revoke the created URL', async () => { - render(imageProps); + await render(imageProps); renderResult.unmount(); expect(mockedRevokeObjectURL).toHaveBeenCalledWith('blob:abc'); }); it('should change the media value according with markdownmedia variations', async () => { - render(mediaProps); + await render(mediaProps); await act(async () => { fireEvent.click(screen.getByText('Add timelink').parentElement!); @@ -158,7 +171,7 @@ describe('MediaField', () => { }); it('should show and error if the image is not valid', async () => { - render(imageProps); + await render(imageProps); const img = renderResult.container.getElementsByTagName('img')[0]; fireEvent.error(img); expect( diff --git a/app/react/Markdown/components/specs/EntityData.spec.tsx b/app/react/Markdown/components/specs/EntityData.spec.tsx index 8ca8adcaf4..adef62f2e3 100644 --- a/app/react/Markdown/components/specs/EntityData.spec.tsx +++ b/app/react/Markdown/components/specs/EntityData.spec.tsx @@ -1,75 +1,82 @@ /** * @jest-environment jsdom */ -import React from 'react'; -import { ReactWrapper } from 'enzyme'; -import { renderConnectedMount } from 'app/utils/test/renderConnected'; +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 { EntityData } from '../EntityData'; +import { EntityData, EntityDataProps } from '../EntityData'; describe('EntityData Markdown', () => { - let component: ReactWrapper< - Readonly<{}> & Readonly<{ children?: React.ReactNode }>, - Readonly<{}>, - React.Component<{}, {}, any> - >; let consoleErrorSpy: jasmine.Spy; + let renderResult: RenderResult; beforeEach(() => { + jest.clearAllMocks(); consoleErrorSpy = jasmine.createSpy('consoleErrorSpy'); spyOn(console, 'error').and.callFake(consoleErrorSpy); + global.fetch = jest.fn(); + (global.fetch as jest.Mock).mockResolvedValue({ ok: true }); }); - const render = (innerComponent: any) => { - component = renderConnectedMount(() => innerComponent, state); + afterEach(() => { + jest.restoreAllMocks(); + }); + + const render = async (props: EntityDataProps) => { + await act(async () => { + ({ renderResult } = renderConnectedContainer(, () => state)); + }); }; describe('root properties Values', () => { - it('should print title and root dates from root of entity', () => { - render(); - expect(component.html()).toBe('Entity 1'); + it('should print title and root dates from root of entity', async () => { + await render({ 'value-of': 'title' }); + expect(screen.getByText('Entity 1')).toBeInTheDocument(); - render(); - expect(component.html()).toBe('1234'); + await render({ 'value-of': 'creationDate' }); + expect(screen.getByText('1234')).toBeInTheDocument(); }); }); describe('metadata property Values', () => { - it('should print formatted metadata properties (sanitizing names)', () => { - render(); - expect(component.html()).toBe('A long description'); + it('should print formatted metadata properties (sanitizing names)', async () => { + await render({ 'value-of': 'description' }); + expect(screen.getByText('A long description')).toBeInTheDocument(); - render(); - expect(component.html()).toBe('Jul 13, 1977'); + await render({ 'value-of': 'date' }); + expect(screen.getByText('Jul 13, 1977')).toBeInTheDocument(); - render(); - expect(component.html()).toContain('src="https://www.google.com"'); + await render({ 'value-of': 'Main Image' }); + expect(screen.getByRole('img').getAttribute('src')).toBe('https://www.google.com'); }); }); describe('labels (property names)', () => { - it('should print translated labels (sanitizing names)', () => { - render(); - expect(component.html()).toContain('Title translated'); + it('should print translated labels (sanitizing names)', async () => { + await render({ 'label-of': 'title' }); + expect(screen.getByText('Title translated')).toBeInTheDocument(); - render(); - expect(component.html()).toContain('Main Image translated'); + await render({ 'label-of': 'Main Image' }); + expect(screen.getByText('Main Image translated')).toBeInTheDocument(); }); }); describe('Error handling (until upstream implementation is implemented)', () => { - it('should fail if no value or propertyName is provided', () => { - render(); - expect(component.html()).toEqual(''); + it('should fail if no value or propertyName is provided', async () => { + await render({}); + //get the body + expect(renderResult.container.innerHTML).toBe(''); expect(consoleErrorSpy).toHaveBeenCalledWith('Error on EntityData: '); expect(consoleErrorSpy.calls.all()[2].args[0].message).toBe( '"value-of" or "label-of" must be provided.' ); }); - it('should fail if both value and propertyName are provided', () => { - render(); - expect(component.html()).toEqual(''); + it('should fail if both value and propertyName are provided', async () => { + await render({ 'value-of': 'something', 'label-of': 'something else' }); + //assert empty html + expect(renderResult.container.innerHTML).toBe(''); expect(consoleErrorSpy).toHaveBeenCalledWith('Error on EntityData: '); expect(consoleErrorSpy.calls.all()[2].args[0].message).toBe( 'Can\'t provide both "value-of" and "label-of".' diff --git a/app/react/Markdown/components/specs/ImageViewer.spec.tsx b/app/react/Markdown/components/specs/ImageViewer.spec.tsx deleted file mode 100644 index 29f1559ce5..0000000000 --- a/app/react/Markdown/components/specs/ImageViewer.spec.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import { ImageViewer, ImageViewerProps } from 'app/Metadata/components/ImageViewer'; - -describe('ImageViewer', () => { - let component: ShallowWrapper; - let imageProps: ImageViewerProps; - - beforeEach(() => { - imageProps = { - alt: 'Image', - className: 'class', - key: '1', - src: 'fixtures/image.jpg', - }; - }); - - const render = () => { - component = shallow(); - }; - - describe('render', () => { - it('should render', () => { - render(); - const props = component.getElement().props; - expect(imageProps).toEqual({ - alt: props.alt, - className: props.className, - key: '1', - src: props.src, - }); - }); - }); -}); diff --git a/app/react/Metadata/components/ImageViewer.tsx b/app/react/Metadata/components/ImageViewer.tsx index d62ffb19c2..222b48cb4a 100644 --- a/app/react/Metadata/components/ImageViewer.tsx +++ b/app/react/Metadata/components/ImageViewer.tsx @@ -1,32 +1,50 @@ import { Translate } from 'app/I18N'; -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; interface ImageViewerProps { - key: string; - className: string; src: string; alt: string; + className?: string; } -const ImageViewer = (props: ImageViewerProps) => { +const ImageViewer = ({ alt, src, className }: ImageViewerProps) => { + const [imageExists, setImageExists] = useState(null); const [errorFlag, setErrorFlag] = useState(false); + useEffect(() => { + const checkImageExists = async (url: string) => { + try { + const response = await fetch(url, { method: 'GET' }); + setImageExists(Boolean(response.ok)); + } catch (error) { + setImageExists(false); + } + }; + + // eslint-disable-next-line no-void + void checkImageExists(src); + }, [src]); + if (imageExists === false) { + return ( +
+ Image not found +
+ ); + } + if (errorFlag) { return (
- This file type is not supported on image fields + This file type is not supported on media fields
); } - return ( - { - setErrorFlag(true); - }} - /> - ); + if (imageExists === null) { + return Loading; + } + + return setErrorFlag(true)} alt={alt} />; }; export { ImageViewer, type ImageViewerProps }; diff --git a/app/react/Metadata/components/specs/ImageViewer.spec.tsx b/app/react/Metadata/components/specs/ImageViewer.spec.tsx new file mode 100644 index 0000000000..f16619b36f --- /dev/null +++ b/app/react/Metadata/components/specs/ImageViewer.spec.tsx @@ -0,0 +1,71 @@ +/** + * @jest-environment jsdom + */ +import React, { act } from 'react'; +import { screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { defaultState, renderConnectedContainer } from 'app/utils/test/renderConnected'; +import { ImageViewer, ImageViewerProps } from '../ImageViewer'; + +describe('ImageViewer', () => { + const props: ImageViewerProps = { + src: 'https://example.com/image.jpg', + alt: 'Example Image', + }; + + beforeEach(() => { + jest.clearAllMocks(); + global.fetch = jest.fn(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + const renderComponent = async () => { + await act(async () => { + renderConnectedContainer(, () => defaultState); + }); + }; + + it('should render loading state initially', async () => { + //delay the fetch response + (global.fetch as jest.Mock).mockImplementation(async () => new Promise(() => {})); + await renderComponent(); + expect(screen.getByText('Loading')).toBeInTheDocument(); + }); + + it('should render the image if it exists', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ ok: true }); + + await renderComponent(); + + const img = screen.getByAltText(props.alt); + expect(img).toBeInTheDocument(); + expect(img).toHaveAttribute('src', props.src); + }); + + it('should render an error message if the image does not exist', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ ok: false }); + + await renderComponent(); + + expect(screen.getByText('Image not found')).toBeInTheDocument(); + }); + + it('should handle image load errors with onError', async () => { + (global.fetch as jest.Mock).mockResolvedValue({ ok: true }); + + await renderComponent(); + + const img = screen.getByAltText(props.alt); + expect(img).toBeInTheDocument(); + + // Simulate image load error + act(() => { + img.dispatchEvent(new Event('error')); + }); + + expect(screen.getByText('This file type is not supported on media fields')).toBeInTheDocument(); + }); +}); diff --git a/app/react/utils/test/renderConnected.tsx b/app/react/utils/test/renderConnected.tsx index 3c148afc01..d83b8c37e6 100644 --- a/app/react/utils/test/renderConnected.tsx +++ b/app/react/utils/test/renderConnected.tsx @@ -5,7 +5,7 @@ import { InitialEntry } from '@remix-run/router'; import { ConnectedComponent, Provider } from 'react-redux'; import thunk from 'redux-thunk'; import Immutable from 'immutable'; -import { render } from '@testing-library/react'; +import { render, RenderResult } from '@testing-library/react'; import { BrowserRouter, MemoryRouter } from 'react-router-dom'; const middlewares = [thunk]; @@ -81,7 +81,7 @@ const renderConnectedContainer = ( ) => { const store = configureStore(middlewares)(stateFunc); - let renderResult; + let renderResult: RenderResult; switch (routerWrapper) { case 'BrowserRouter': diff --git a/contents/ui-translations/ar.csv b/contents/ui-translations/ar.csv index 845864c64f..1133276fa8 100644 --- a/contents/ui-translations/ar.csv +++ b/contents/ui-translations/ar.csv @@ -377,6 +377,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.","If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io." "If you want to continue, please type",إذا كنت ترغب في المتابعة، يرجى كتابة +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,استيراد Import asset,Import asset @@ -846,7 +847,6 @@ This content is automatically generated,أنشئ هذا المحتوى تلقا This entity is not public.,هذا الكيان (الإدخال) ليس عام This entity is restricted from public view.,هذا الكيان (الإدخال) ممنوع من المعاينة العامة This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,سيظهر هذا الخيار إشعاراً حول استخدام ملفات تعريف الارتباط في قاعدة بياناتك diff --git a/contents/ui-translations/en.csv b/contents/ui-translations/en.csv index 985dd7b545..f50d0f1924 100644 --- a/contents/ui-translations/en.csv +++ b/contents/ui-translations/en.csv @@ -380,6 +380,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.","If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io." "If you want to continue, please type","If you want to continue, please type" +Image not found,Image not found Impact of property change warning,"By making this change, any values from the previous thesaurus already assigned to entities will be lost." Import,Import Import asset,Import asset @@ -849,7 +850,6 @@ This content is automatically generated,This content is automatically generated This entity is not public.,This entity is not public. This entity is restricted from public view.,This entity is restricted from public view. This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,This option will show a notification about the use of cookies in your instance. diff --git a/contents/ui-translations/es.csv b/contents/ui-translations/es.csv index 101293c50c..1f58a9d9ca 100644 --- a/contents/ui-translations/es.csv +++ b/contents/ui-translations/es.csv @@ -376,6 +376,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.","Si utilizas una URL externa, utiliza una dirección completa. Ejemplo: http://www.uwazi.io." "If you want to continue, please type","Si quieres continuar, por favor escribe" +Image not found,Image not found Impact of property change warning,Todas las entidades y documentos que ya tienen esta propiedad asignada perderán su valor actual Import,Importar Import asset,Import asset @@ -844,7 +845,6 @@ This content is automatically generated,Este contenido se genera automáticament This entity is not public.,Esta entidad no es pública. This entity is restricted from public view.,Esta entidad está restringida de la vista pública. This field is required,Este campo es obligatorio -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,Esta opción mostrará una notificación sobre el uso de cookies en la instancia. diff --git a/contents/ui-translations/fr.csv b/contents/ui-translations/fr.csv index d30c2d231c..b775dc5d8f 100644 --- a/contents/ui-translations/fr.csv +++ b/contents/ui-translations/fr.csv @@ -377,6 +377,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.","Si c'est une URL externe, utilisez une URL complète. Par exemple, http://www.uwazi.io." "If you want to continue, please type","Si vous voulez continuer, veuillez taper" +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,Importer Import asset,Import asset @@ -846,7 +847,6 @@ This content is automatically generated,Ce contenu est généré automatiquement This entity is not public.,Cette entité n'est pas publique. This entity is restricted from public view.,Cette entité n'est pas accessible au public. This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,Cette option affichera une notification sur l'utilisation des cookies dans votre instance. diff --git a/contents/ui-translations/ko.csv b/contents/ui-translations/ko.csv index 89550a8b28..cc0dbcb863 100644 --- a/contents/ui-translations/ko.csv +++ b/contents/ui-translations/ko.csv @@ -378,6 +378,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.",외부 URL 인 경우 전체 경로 (http://www. 부터 시작하는 전체 URL) 를 넣으십시오. "If you want to continue, please type",계속하려면 다음을 입력하십시오: +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,업로드 Import asset,Import asset @@ -847,7 +848,6 @@ This content is automatically generated,콘텐츠가 자동으로 생성되었 This entity is not public.,공개된 엔티티가 아닙니다. This entity is restricted from public view.,이 엔티티는 공개 모드에서 제한됩니다. This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,인스턴스 내 쿠키 사용 알림이 필요할 경우 이 옵션을 선택하십시오. diff --git a/contents/ui-translations/my.csv b/contents/ui-translations/my.csv index 3b22c834ad..493df87535 100644 --- a/contents/ui-translations/my.csv +++ b/contents/ui-translations/my.csv @@ -378,6 +378,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.",ပြင်ပ URL ဖြစ်ပါက URL ပုံစံအပြည့်အစုံကို သုံးပါ။ ဥပမာ- http://www.uwazi.io။ "If you want to continue, please type",ဆက်လက်ဆောင်ရွက်လိုပါက ကျေးဇူးပြု၍ စာရိုက်ပါ +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,ထည့်သွင်းရန် Import asset,Import asset @@ -847,7 +848,6 @@ This content is automatically generated,ဤအကြောင်းအရာက This entity is not public.,ဤဖြည့်သွင်းချက်ကို အများမမြင်နိုင်ပါ။ This entity is restricted from public view.,ဤဖြည့်သွင်းချက်ကို အများမမြင်နိုင်အောင် ကန့်သတ်ထားသည်။ This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,ဤရွေးချယ်မှုသည် သင့်အခြေအနေတွင် ကွတ်ကီးများ အသုံးပြုမှုအကြောင်း အသိပေးချက် ပြသပါမည်။ diff --git a/contents/ui-translations/ru.csv b/contents/ui-translations/ru.csv index b76f1cd691..93b5538b6a 100644 --- a/contents/ui-translations/ru.csv +++ b/contents/ui-translations/ru.csv @@ -375,6 +375,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.","Если это внешний URL, используйте полностью сформированный URL. Т. е. http://www.uwazi.io." "If you want to continue, please type","Если вы хотите продолжить, пожалуйста, введите" +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,Импортировать Import asset,Import asset @@ -844,7 +845,6 @@ This content is automatically generated,Этот контент генериру This entity is not public.,Этот объект не является общедоступным. This entity is restricted from public view.,Этот объект ограничен для общедоступного просмотра. This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,Эта опция покажет уведомление об использовании файлов cookie в вашей базе данных. diff --git a/contents/ui-translations/th.csv b/contents/ui-translations/th.csv index f30e53be67..08bc7616d6 100644 --- a/contents/ui-translations/th.csv +++ b/contents/ui-translations/th.csv @@ -378,6 +378,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.",หากเป็น URL จากภายนอก กรุณาใช้ URL แบบเต็มเช่น http://www.uwazi.io "If you want to continue, please type",ถ้าคุณต้องการดำเนินการต่อ กรุณาพิมพ์ +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,นำเข้า Import asset,Import asset @@ -847,7 +848,6 @@ This content is automatically generated,เนื้อหานี้ถูก This entity is not public.,รายการนี้ไม่ได้เปิดการเข้าถึงแบบสาธารณะ This entity is restricted from public view.,รายการนี้ถูกจำกัดการเข้าถึงแบบสาธารณะ This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,ตัวเลือกนี้จะแสดงการแจ้งเตือนเกี่ยวกับการใช้คุ้กกี้ในระบบของคุณ diff --git a/contents/ui-translations/tr.csv b/contents/ui-translations/tr.csv index b906641b55..cafc2780b9 100644 --- a/contents/ui-translations/tr.csv +++ b/contents/ui-translations/tr.csv @@ -378,6 +378,7 @@ ID,ID ID:,ID: "If it is an external URL, use a fully formed URL. Ie. http://www.uwazi.io.","Harici bir URL ise, tam olarak oluşturulmuş bir URL kullanın: http://www.uwazi.io." "If you want to continue, please type","Devam etmek istiyorsanız, lütfen yazın" +Image not found,Image not found Impact of property change warning,All entities and documents that have already this property assigned will loose its current value Import,İçe aktar Import asset,Import asset @@ -847,7 +848,6 @@ This content is automatically generated,Bu içerik otomatik olarak oluşturulur This entity is not public.,Bu varlık açık değil. This entity is restricted from public view.,Bu varlığın genel görünümü kısıtlanmıştır. This field is required,This field is required -This file type is not supported on image fields,This file type is not supported on image fields This file type is not supported on media fields,This file type is not supported on media fields This group is empty,This group is empty This option will show a notification about the use of cookies in your instance.,"Bu seçenek, oluşumunuzda çerez kullanımı hakkında bir bildirim gösterecektir." diff --git a/cypress.config.ts b/cypress.config.ts index e7749a2f3e..12dcb91048 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -3,6 +3,8 @@ import { defineConfig } from 'cypress'; const { initPlugin } = require('cypress-plugin-snapshots/plugin'); +const retries = process.env.CYPRESS_RETRIES ? parseInt(process.env.CYPRESS_RETRIES, 10) : 0; + export default defineConfig({ viewportWidth: 1280, viewportHeight: 768, @@ -11,7 +13,7 @@ export default defineConfig({ e2e: { baseUrl: 'http://localhost:3000', video: true, - retries: 2, + retries, screenshotOnRunFailure: false, testIsolation: false, specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', diff --git a/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the second entity with files #0.png b/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the second entity with files #0.png deleted file mode 100644 index 161190c2ae..0000000000 Binary files a/cypress/e2e/pages/__image_snapshots__/Public Form check created entities should check the second entity with files #0.png and /dev/null differ diff --git a/cypress/e2e/pages/public-form.cy.ts b/cypress/e2e/pages/public-form.cy.ts index b6744e411e..d73704fae2 100644 --- a/cypress/e2e/pages/public-form.cy.ts +++ b/cypress/e2e/pages/public-form.cy.ts @@ -26,6 +26,7 @@ describe('Public Form', () => { }); cy.contains('button', 'Save').click(); + cy.contains('Dismiss').click(); }); }); @@ -41,10 +42,15 @@ describe('Public Form', () => { ); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(501); + cy.intercept('GET', '/api/page*').as('fetchPage'); cy.contains('[data-testid=settings-content-footer] button.bg-success-700', 'Save').click(); cy.contains('Saved successfully'); cy.contains('Dismiss').click(); cy.contains('Basic').click(); + + //wait for /pages GET request + cy.wait('@fetchPage'); + cy.get('input[id="page-url"]').should('not.have.value', ''); cy.get('input[id="page-url"]').then(url => { cy.contains('a', 'Menu').click(); cy.contains('button', 'Add link').click(); @@ -96,9 +102,11 @@ describe('Public Form', () => { ); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(501); + cy.intercept('GET', '/api/page*').as('fetchPage'); cy.contains('button.bg-success-700', 'Save').click(); cy.contains('Saved successfully'); cy.contains('Dismiss').click(); + cy.wait('@fetchPage'); }); it('should revisit the page and fill the text, select and date fields', () => { @@ -174,9 +182,21 @@ describe('Public Form', () => { it('should check the second entity with files', () => { cy.get('.library-viewer').scrollTo('top'); cy.contains('h2', 'Entity with image and media fields').click(); + cy.intercept('GET', '/api/files/*').as('waitForImages'); cy.contains('aside.is-active a', 'View').click(); - cy.get('.attachments-list-parent').eq(0).scrollIntoView(); - cy.get('.attachments-list-parent').eq(0).toMatchImageSnapshot(); + //wait for .multimedia-img to be visible + cy.get('.multimedia-img').should('be.visible'); + + cy.wait('@waitForImages'); + + //cy.get('.attachments-list-parent').eq(0).scrollIntoView(); + //cy.get('.attachments-list-parent').eq(0).toMatchImageSnapshot(); + + // get the names of the supporting files in first span of .attachment-name + cy.get('.attachment-name span:first-of-type').then($spans => { + const names = $spans.toArray().map(span => span.textContent); + expect(names).to.deep.equal(['batman.jpg', 'batman.jpg', 'short-video.mp4']); + }); }); }); diff --git a/package.json b/package.json index bd7b75f5db..dc6db343da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uwazi", - "version": "1.194.7", + "version": "1.195.0", "description": "Uwazi is a free, open-source solution for organising, analysing and publishing your documents.", "keywords": [ "react" @@ -173,7 +173,7 @@ "mime-types": "^2.1.35", "moment": "^2.30.1", "moment-timezone": "0.5.46", - "monaco-editor": "^0.52.0", + "monaco-editor": "0.50.0", "monaco-editor-webpack-plugin": "^7.1.0", "mongodb": "6.3.0", "mongoose": "8.1.2", diff --git a/yarn.lock b/yarn.lock index 44c7cc77b8..a9c2c490f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12190,10 +12190,10 @@ monaco-editor-webpack-plugin@^7.1.0: dependencies: loader-utils "^2.0.2" -monaco-editor@^0.52.0: - version "0.52.0" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.0.tgz#d47c02b191eae208d68878d679b3ee7456031be7" - integrity sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw== +monaco-editor@0.50.0: + version "0.50.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.50.0.tgz#44e62b124c8aed224e1d310bbbe6ffd6d5122413" + integrity sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA== mongodb-connection-string-url@^3.0.0: version "3.0.0"