-
Notifications
You must be signed in to change notification settings - Fork 605
✨(frontend) warn the user when trying to upload a file size that exceeds the limit #2522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
magopian
wants to merge
8
commits into
suitenumerique:main
Choose a base branch
from
magopian:fix/ux-feedback-on-too-big-file-upload
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
58260b1
🔧(backend) expose DOCUMENT_IMAGE_MAX_SIZE in config endpoint
magopian ee82d4e
🐛(frontend) handle non-JSON error responses in errorCauses
magopian 77f63b1
✨(frontend) validate file size before upload
magopian 1097e43
🚸(frontend) display an error message when the uploaded file is too big
magopian 23f25a2
🚸(frontend) handle the upload of a too big file in BlockNoteEditor
magopian 691955e
✅(frontend) add e2e test for too big file uploads
magopian 60a3aae
🚸(frontend) handle the pasting of a too big file
magopian 6822be9
📝(frontend) update the CHANGELOG
magopian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
src/frontend/apps/e2e/__tests__/app-impress/doc-editor-upload.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import { Page, expect, test } from '@playwright/test'; | ||
|
|
||
| import { createDoc, overrideConfig } from './utils-common'; | ||
| import { getEditor } from './utils-editor'; | ||
|
|
||
| const dropFileInEditor = async (page: Page) => { | ||
| const dataTransfer = await page.evaluateHandle(() => { | ||
| const dt = new DataTransfer(); | ||
| // 1MB + 1 byte, exceeds the 1MB limit set in overrideConfig. | ||
| const file = new File([new Uint8Array(1048577)], 'video.mp4', { | ||
| type: 'video/mp4', | ||
| }); | ||
| dt.items.add(file); | ||
| return dt; | ||
| }); | ||
|
|
||
| await page | ||
| .getByLabel('Document editor') | ||
| .dispatchEvent('drop', { dataTransfer }); | ||
| }; | ||
|
|
||
| const pasteFileInEditor = async (page: Page) => { | ||
| await page.getByLabel('Document editor').focus(); | ||
|
|
||
| await page.getByLabel('Document editor').evaluate((el) => { | ||
| const dt = new DataTransfer(); | ||
| const file = new File([new Uint8Array(1048577)], 'video.mp4', { | ||
| type: 'video/mp4', | ||
| }); | ||
| dt.items.add(file); | ||
|
|
||
| const event = new ClipboardEvent('paste', { | ||
| clipboardData: dt, | ||
| bubbles: true, | ||
| cancelable: true, | ||
| }); | ||
| el.dispatchEvent(event); | ||
| }); | ||
| }; | ||
|
|
||
| test.describe('Doc Editor - File Upload', () => { | ||
| test('dropping a file that is too large shows an error and does not leave a loading block', async ({ | ||
| page, | ||
| browserName, | ||
| }) => { | ||
| await overrideConfig(page, { DOCUMENT_IMAGE_MAX_SIZE: 1048576 }); // Override the size limit to 1MB for the test. | ||
| await page.goto('/'); | ||
| await createDoc(page, 'doc-upload-too-large', browserName, 1); | ||
| await getEditor({ page }); | ||
|
|
||
| await dropFileInEditor(page); | ||
|
|
||
| await expect( | ||
| page.getByText('File size exceeds the maximum allowed size of 1MB.'), | ||
| ).toBeVisible(); | ||
|
|
||
| await expect(page.getByText('Loading...')).toBeHidden(); | ||
| }); | ||
|
|
||
| test('dismissing the error and dropping the same file again shows the error again', async ({ | ||
| page, | ||
| browserName, | ||
| }) => { | ||
| await overrideConfig(page, { DOCUMENT_IMAGE_MAX_SIZE: 1048576 }); // Override the size limit to 1MB for the test. | ||
| await page.goto('/'); | ||
| await createDoc(page, 'doc-upload-error-retry', browserName, 1); | ||
| await getEditor({ page }); | ||
|
|
||
| await dropFileInEditor(page); | ||
| await expect( | ||
| page.getByText('File size exceeds the maximum allowed size of 1MB.'), | ||
| ).toBeVisible(); | ||
|
|
||
| // Dismiss the error | ||
| await page.locator('.--docs--text-errors').getByRole('button').click(); | ||
| await expect( | ||
| page.getByText('File size exceeds the maximum allowed size of 1MB.'), | ||
| ).toBeHidden(); | ||
|
|
||
| // Drop the same file again | ||
| await dropFileInEditor(page); | ||
| await expect( | ||
| page.getByText('File size exceeds the maximum allowed size of 1MB.'), | ||
| ).toBeVisible(); | ||
| }); | ||
|
|
||
| test('pasting a file that is too large shows an error and does not leave a loading block', async ({ | ||
| page, | ||
| browserName, | ||
| }) => { | ||
| test.skip( | ||
| browserName === 'firefox', | ||
| 'Firefox does not expose clipboardData.items on synthetic ClipboardEvents, making this untestable via dispatchEvent.', | ||
| ); | ||
| await overrideConfig(page, { DOCUMENT_IMAGE_MAX_SIZE: 1048576 }); | ||
| await page.goto('/'); | ||
| await createDoc(page, 'doc-upload-paste-too-large', browserName, 1); | ||
| await getEditor({ page }); | ||
|
|
||
| await pasteFileInEditor(page); | ||
|
|
||
| await expect( | ||
| page.getByText('File size exceeds the maximum allowed size of 1MB.'), | ||
| ).toBeVisible(); | ||
|
|
||
| await expect(page.getByText('Loading...')).toBeHidden(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useUploadFile.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| import { renderHook, waitFor } from '@testing-library/react'; | ||
| import fetchMock from 'fetch-mock'; | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import { APIError } from '@/api'; | ||
| import { AppWrapper } from '@/tests/utils'; | ||
|
|
||
| import { useUploadFile } from '../useUploadFile'; | ||
|
|
||
| vi.mock('@/core', () => ({ | ||
| useConfig: () => ({ | ||
| data: { DOCUMENT_IMAGE_MAX_SIZE: 1 * 1024 * 1024 }, // 1MB limit for test | ||
| }), | ||
| })); | ||
|
|
||
| describe('useUploadFile', () => { | ||
| // Fake file with a size slightly under the limit | ||
| const smallFile = new File( | ||
| [new ArrayBuffer(1 * 1024 * 1024 - 1)], | ||
| 'big.png', | ||
| { | ||
| type: 'image/png', | ||
| }, | ||
| ); | ||
| // Fake file with a size slightly over the limit | ||
| const bigFile = new File([new ArrayBuffer(1 * 1024 * 1024 + 1)], 'big.png', { | ||
| type: 'image/png', | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| fetchMock.restore(); | ||
| }); | ||
|
|
||
| it("proceeds to upload when file doesn't exceed the size limit", async () => { | ||
| fetchMock.post( | ||
| 'http://test.jest/api/v1.0/documents/doc-id/attachment-upload/', | ||
| { body: { file: '/media/test.jpg' } }, | ||
| ); | ||
| const { result } = renderHook(() => useUploadFile('doc-id'), { | ||
| wrapper: AppWrapper, | ||
| }); | ||
|
|
||
| await result.current.uploadFile(smallFile); | ||
| expect(fetchMock.calls()).toHaveLength(1); | ||
| }); | ||
|
|
||
| it('throws an APIError before uploading when file exceeds the size limit', async () => { | ||
| const { result } = renderHook(() => useUploadFile('doc-id'), { | ||
| wrapper: AppWrapper, | ||
| }); | ||
|
|
||
| await expect(result.current.uploadFile(bigFile)).rejects.toThrow(APIError); | ||
| expect(fetchMock.calls()).toHaveLength(0); | ||
| }); | ||
|
|
||
| it('sets errorAttachment with a user-friendly message when file exceeds the size limit', async () => { | ||
| const { result } = renderHook(() => useUploadFile('doc-id'), { | ||
| wrapper: AppWrapper, | ||
| }); | ||
|
|
||
| await result.current.uploadFile(bigFile).catch(() => {}); | ||
|
|
||
| await waitFor(() => { | ||
| expect(result.current.isErrorAttachment).toBe(true); | ||
| }); | ||
|
|
||
| expect(result.current.errorAttachment?.cause).toEqual([ | ||
| 'File size exceeds the maximum allowed size of 1MB.', | ||
| ]); | ||
| }); | ||
|
|
||
| it('exposes checkFileSize that does not throw for files within the limit', () => { | ||
| const { result } = renderHook(() => useUploadFile('doc-id'), { | ||
| wrapper: AppWrapper, | ||
| }); | ||
|
|
||
| expect(() => result.current.checkFileSize(smallFile)).not.toThrow(); | ||
| }); | ||
|
|
||
| it('exposes checkFileSize that throws and sets errorAttachment for files over the limit', async () => { | ||
| const { result } = renderHook(() => useUploadFile('doc-id'), { | ||
| wrapper: AppWrapper, | ||
| }); | ||
|
|
||
| expect(() => result.current.checkFileSize(bigFile)).toThrow(APIError); | ||
|
|
||
| await waitFor(() => { | ||
| expect(result.current.isErrorAttachment).toBe(true); | ||
| }); | ||
|
|
||
| expect(result.current.errorAttachment?.cause).toEqual([ | ||
| 'File size exceeds the maximum allowed size of 1MB.', | ||
| ]); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems unnecessary to listen these events to display the error message about the upload size.
In my understanding it is to not see the "loading" part, but we should act when we get the error, not before, so either by updating or removing the block, but only when the error occurs.