From e244856ef1b2f2f3bb230e76ea04523839b5d802 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 12 Dec 2025 14:56:08 +0800 Subject: [PATCH] chore: add test case for download components (#29569) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../csv-uploader.spec.tsx | 121 ++++++++++++++++++ .../csv-uploader.tsx | 2 +- .../run-batch/res-download/index.spec.tsx | 53 ++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx create mode 100644 web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx new file mode 100644 index 0000000000..91e1e9d8fe --- /dev/null +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.spec.tsx @@ -0,0 +1,121 @@ +import React from 'react' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import CSVUploader, { type Props } from './csv-uploader' +import { ToastContext } from '@/app/components/base/toast' + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +describe('CSVUploader', () => { + const notify = jest.fn() + const updateFile = jest.fn() + + const getDropElements = () => { + const title = screen.getByText('appAnnotation.batchModal.csvUploadTitle') + const dropZone = title.parentElement?.parentElement as HTMLDivElement | null + if (!dropZone || !dropZone.parentElement) + throw new Error('Drop zone not found') + const dropContainer = dropZone.parentElement as HTMLDivElement + return { dropZone, dropContainer } + } + + const renderComponent = (props?: Partial) => { + const mergedProps: Props = { + file: undefined, + updateFile, + ...props, + } + return render( + + + , + ) + } + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('should open the file picker when clicking browse', () => { + const clickSpy = jest.spyOn(HTMLInputElement.prototype, 'click') + renderComponent() + + fireEvent.click(screen.getByText('appAnnotation.batchModal.browse')) + + expect(clickSpy).toHaveBeenCalledTimes(1) + clickSpy.mockRestore() + }) + + it('should toggle dragging styles and upload the dropped file', async () => { + const file = new File(['content'], 'input.csv', { type: 'text/csv' }) + renderComponent() + const { dropZone, dropContainer } = getDropElements() + + fireEvent.dragEnter(dropContainer) + expect(dropZone.className).toContain('border-components-dropzone-border-accent') + expect(dropZone.className).toContain('bg-components-dropzone-bg-accent') + + fireEvent.drop(dropContainer, { dataTransfer: { files: [file] } }) + + await waitFor(() => expect(updateFile).toHaveBeenCalledWith(file)) + expect(dropZone.className).not.toContain('border-components-dropzone-border-accent') + }) + + it('should ignore drop events without dataTransfer', () => { + renderComponent() + const { dropContainer } = getDropElements() + + fireEvent.drop(dropContainer) + + expect(updateFile).not.toHaveBeenCalled() + }) + + it('should show an error when multiple files are dropped', async () => { + const fileA = new File(['a'], 'a.csv', { type: 'text/csv' }) + const fileB = new File(['b'], 'b.csv', { type: 'text/csv' }) + renderComponent() + const { dropContainer } = getDropElements() + + fireEvent.drop(dropContainer, { dataTransfer: { files: [fileA, fileB] } }) + + await waitFor(() => expect(notify).toHaveBeenCalledWith({ + type: 'error', + message: 'datasetCreation.stepOne.uploader.validation.count', + })) + expect(updateFile).not.toHaveBeenCalled() + }) + + it('should propagate file selection changes through input change event', () => { + const file = new File(['row'], 'selected.csv', { type: 'text/csv' }) + const { container } = renderComponent() + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement + + fireEvent.change(fileInput, { target: { files: [file] } }) + + expect(updateFile).toHaveBeenCalledWith(file) + }) + + it('should render selected file details and allow change/removal', () => { + const file = new File(['data'], 'report.csv', { type: 'text/csv' }) + const { container } = renderComponent({ file }) + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement + + expect(screen.getByText('report')).toBeInTheDocument() + expect(screen.getByText('.csv')).toBeInTheDocument() + + const clickSpy = jest.spyOn(HTMLInputElement.prototype, 'click') + fireEvent.click(screen.getByText('datasetCreation.stepOne.uploader.change')) + expect(clickSpy).toHaveBeenCalled() + clickSpy.mockRestore() + + const valueSetter = jest.spyOn(fileInput, 'value', 'set') + const removeTrigger = screen.getByTestId('remove-file-button') + fireEvent.click(removeTrigger) + + expect(updateFile).toHaveBeenCalledWith() + expect(valueSetter).toHaveBeenCalledWith('') + }) +}) diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx index b98eb815f9..ccad46b860 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx @@ -114,7 +114,7 @@ const CSVUploader: FC = ({
-
+
diff --git a/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx b/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx new file mode 100644 index 0000000000..65acac8bb6 --- /dev/null +++ b/web/app/components/share/text-generation/run-batch/res-download/index.spec.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import ResDownload from './index' + +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +const mockType = { Link: 'mock-link' } +let capturedProps: Record | undefined + +jest.mock('react-papaparse', () => ({ + useCSVDownloader: () => { + const CSVDownloader = ({ children, ...props }: React.PropsWithChildren>) => { + capturedProps = props + return
{children}
+ } + return { + CSVDownloader, + Type: mockType, + } + }, +})) + +describe('ResDownload', () => { + const values = [{ text: 'Hello' }] + + beforeEach(() => { + jest.clearAllMocks() + capturedProps = undefined + }) + + it('should render desktop download button with CSV downloader props', () => { + render() + + expect(screen.getByTestId('csv-downloader')).toBeInTheDocument() + expect(screen.getByText('common.operation.download')).toBeInTheDocument() + expect(capturedProps?.data).toEqual(values) + expect(capturedProps?.filename).toBe('result') + expect(capturedProps?.bom).toBe(true) + expect(capturedProps?.type).toBe(mockType.Link) + }) + + it('should render mobile action button without desktop label', () => { + render() + + expect(screen.getByTestId('csv-downloader')).toBeInTheDocument() + expect(screen.queryByText('common.operation.download')).not.toBeInTheDocument() + expect(screen.getByRole('button')).toBeInTheDocument() + }) +})