diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 800bbc746b..0a6df42d77 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -1986,9 +1986,6 @@ }, "react-refresh/only-export-components": { "count": 1 - }, - "react/use-memo": { - "count": 1 } }, "web/app/components/datasets/documents/detail/completed/segment-list.tsx": { diff --git a/web/__tests__/datasets/segment-crud.test.tsx b/web/__tests__/datasets/segment-crud.test.tsx index fe21dd5079..1308b0c103 100644 --- a/web/__tests__/datasets/segment-crud.test.tsx +++ b/web/__tests__/datasets/segment-crud.test.tsx @@ -111,77 +111,27 @@ describe('Segment CRUD Flow', () => { }) describe('Segment Selection → Batch Operations', () => { - const segments = [ - createSegment('seg-1'), - createSegment('seg-2'), - createSegment('seg-3'), - ] - it('should manage individual segment selection', () => { - const { result } = renderHook(() => useSegmentSelection(segments)) + const { result } = renderHook(() => useSegmentSelection()) act(() => { - result.current.onSelected('seg-1') + result.current.onSelectedSegmentIdsChange(['seg-1']) }) expect(result.current.selectedSegmentIds).toContain('seg-1') act(() => { - result.current.onSelected('seg-2') + result.current.onSelectedSegmentIdsChange(['seg-1', 'seg-2']) }) expect(result.current.selectedSegmentIds).toContain('seg-1') expect(result.current.selectedSegmentIds).toContain('seg-2') expect(result.current.selectedSegmentIds).toHaveLength(2) }) - it('should toggle selection on repeated click', () => { - const { result } = renderHook(() => useSegmentSelection(segments)) - - act(() => { - result.current.onSelected('seg-1') - }) - expect(result.current.selectedSegmentIds).toContain('seg-1') - - act(() => { - result.current.onSelected('seg-1') - }) - expect(result.current.selectedSegmentIds).not.toContain('seg-1') - }) - - it('should support select all toggle', () => { - const { result } = renderHook(() => useSegmentSelection(segments)) - - act(() => { - result.current.onSelectedAll() - }) - expect(result.current.selectedSegmentIds).toHaveLength(3) - expect(result.current.isAllSelected).toBe(true) - - act(() => { - result.current.onSelectedAll() - }) - expect(result.current.selectedSegmentIds).toHaveLength(0) - expect(result.current.isAllSelected).toBe(false) - }) - - it('should detect partial selection via isSomeSelected', () => { - const { result } = renderHook(() => useSegmentSelection(segments)) - - act(() => { - result.current.onSelected('seg-1') - }) - - // After selecting one of three, isSomeSelected should be true - expect(result.current.selectedSegmentIds).toEqual(['seg-1']) - expect(result.current.isSomeSelected).toBe(true) - expect(result.current.isAllSelected).toBe(false) - }) - it('should clear selection via onCancelBatchOperation', () => { - const { result } = renderHook(() => useSegmentSelection(segments)) + const { result } = renderHook(() => useSegmentSelection()) act(() => { - result.current.onSelected('seg-1') - result.current.onSelected('seg-2') + result.current.onSelectedSegmentIdsChange(['seg-1', 'seg-2']) }) expect(result.current.selectedSegmentIds).toHaveLength(2) @@ -271,7 +221,7 @@ describe('Segment CRUD Flow', () => { useSearchFilter({ onPageChange: vi.fn() }), ) const { result: selectionResult } = renderHook(() => - useSegmentSelection(segments), + useSegmentSelection(), ) const { result: modalResult } = renderHook(() => useModalState({ onNewSegmentModalChange: vi.fn() }), @@ -284,7 +234,7 @@ describe('Segment CRUD Flow', () => { // Select a segment act(() => { - selectionResult.current.onSelected('seg-1') + selectionResult.current.onSelectedSegmentIdsChange(['seg-1']) }) // Open detail modal diff --git a/web/app/components/datasets/documents/detail/completed/__tests__/index.spec.tsx b/web/app/components/datasets/documents/detail/completed/__tests__/index.spec.tsx index 900c974252..0d6a37d64e 100644 --- a/web/app/components/datasets/documents/detail/completed/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/detail/completed/__tests__/index.spec.tsx @@ -137,36 +137,40 @@ vi.mock('../hooks/use-child-segment-data', () => ({ }, })) -vi.mock('../components/menu-bar', () => ({ - default: ({ totalText, onInputChange, inputValue, isLoading, onSelectedAll, onChangeStatus }: { - totalText: string - onInputChange: (value: string) => void - inputValue: string - isLoading: boolean - onSelectedAll?: () => void - onChangeStatus?: (item: { value: string | number, name: string }) => void - }) => ( -
- {totalText} - onInputChange(e.target.value)} - disabled={isLoading} - /> - {onSelectedAll && ( - - )} - {onChangeStatus && ( - <> - - - - - )} -
- ), -})) +vi.mock('../components/menu-bar', async () => { + const { Checkbox } = await import('@langgenius/dify-ui/checkbox') + + return { + default: ({ hasSelectableSegments, totalText, onInputChange, inputValue, isLoading, onChangeStatus }: { + hasSelectableSegments: boolean + totalText: string + onInputChange: (value: string) => void + inputValue: string + isLoading: boolean + onChangeStatus?: (item: { value: string | number, name: string }) => void + }) => ( +
+ {totalText} + onInputChange(e.target.value)} + disabled={isLoading} + /> + {hasSelectableSegments + ? + : } + {onChangeStatus && ( + <> + + + + + )} +
+ ), + } +}) vi.mock('../components/drawer-group', () => ({ DrawerGroup: () =>
, @@ -751,6 +755,17 @@ describe('Batch Action Callbacks', () => { }) }) + it('should not render select all when there are no current page segments', () => { + mockSegmentListData.data = [] + mockSegmentListData.total = 0 + + render(, { wrapper: createWrapper() }) + + expect(screen.queryByTestId('select-all-button')).not.toBeInTheDocument() + expect(screen.getByTestId('select-all-spacer')).toBeInTheDocument() + expect(screen.queryByTestId('batch-action')).not.toBeInTheDocument() + }) + it('should call onChangeSwitch with true when batch enable is clicked', async () => { render(, { wrapper: createWrapper() }) diff --git a/web/app/components/datasets/documents/detail/completed/__tests__/segment-list.spec.tsx b/web/app/components/datasets/documents/detail/completed/__tests__/segment-list.spec.tsx index d76bac26e6..4e9f880536 100644 --- a/web/app/components/datasets/documents/detail/completed/__tests__/segment-list.spec.tsx +++ b/web/app/components/datasets/documents/detail/completed/__tests__/segment-list.spec.tsx @@ -123,8 +123,6 @@ describe('SegmentList', () => { ref: null, isLoading: false, items: [createMockSegment('seg-1', 'Segment 1 content')], - selectedSegmentIds: [], - onSelected: vi.fn(), onClick: vi.fn(), onChangeSwitch: vi.fn(), onDelete: vi.fn(), @@ -289,18 +287,10 @@ describe('SegmentList', () => { expect(screen.getAllByRole('checkbox')).toHaveLength(defaultProps.items.length) }) - it('should pass selectedSegmentIds to check state', () => { - const { container } = render() + it('should label each segment checkbox', () => { + render() - // Assert - component should render with selected state - expect(container.firstChild).toBeInTheDocument() - }) - - it('should handle empty selectedSegmentIds', () => { - const { container } = render() - - // Assert - component should render - expect(container.firstChild).toBeInTheDocument() + expect(screen.getByRole('checkbox', { name: 'datasetDocuments.segment.chunk 1' })).toBeInTheDocument() }) }) diff --git a/web/app/components/datasets/documents/detail/completed/components/__tests__/menu-bar.spec.tsx b/web/app/components/datasets/documents/detail/completed/components/__tests__/menu-bar.spec.tsx index f3bb01a804..9ca5601e6a 100644 --- a/web/app/components/datasets/documents/detail/completed/components/__tests__/menu-bar.spec.tsx +++ b/web/app/components/datasets/documents/detail/completed/components/__tests__/menu-bar.spec.tsx @@ -1,3 +1,4 @@ +import { CheckboxGroup } from '@langgenius/dify-ui/checkbox-group' import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import MenuBar from '../menu-bar' @@ -16,9 +17,7 @@ vi.mock('../../status-item', () => ({ describe('MenuBar', () => { const defaultProps = { - isAllSelected: false, - isSomeSelected: false, - onSelectedAll: vi.fn(), + hasSelectableSegments: true, isLoading: false, totalText: '10 Chunks', statusList: [ @@ -38,49 +37,63 @@ describe('MenuBar', () => { vi.clearAllMocks() }) + const renderMenuBar = (props: Partial = {}) => { + return render( + + + , + ) + } + it('should render total text', () => { - render() + renderMenuBar() expect(screen.getByText('10 Chunks')).toBeInTheDocument() }) it('should render checkbox', () => { - render() + renderMenuBar() expect(screen.getByRole('checkbox', { name: 'common.operation.selectAll' })).toBeInTheDocument() }) + it('should not render select all checkbox when there are no selectable segments', () => { + renderMenuBar({ hasSelectableSegments: false }) + + expect(screen.queryByRole('checkbox', { name: 'common.operation.selectAll' })).not.toBeInTheDocument() + }) + it('should call onInputChange when input changes', () => { - render() + renderMenuBar() const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'test search' } }) expect(defaultProps.onInputChange).toHaveBeenCalledWith('test search') }) it('should render display toggle', () => { - render() + renderMenuBar() expect(screen.getByTestId('display-toggle')).toBeInTheDocument() }) it('should call toggleCollapsed when display toggle clicked', () => { - render() + renderMenuBar() fireEvent.click(screen.getByTestId('display-toggle')) expect(defaultProps.toggleCollapsed).toHaveBeenCalled() }) it('should call onInputChange with empty string when input is cleared', () => { - render() + renderMenuBar({ inputValue: 'some text' }) const clearButton = screen.getByRole('button', { name: 'common.operation.clear' }) fireEvent.click(clearButton) expect(defaultProps.onInputChange).toHaveBeenCalledWith('') }) it('should render select with status items via renderOption', () => { - render() + renderMenuBar() expect(screen.getByText('All')).toBeInTheDocument() }) it('should call renderOption for each item when dropdown is opened', async () => { - render() + renderMenuBar() const selectButton = screen.getByRole('combobox') fireEvent.click(selectButton) diff --git a/web/app/components/datasets/documents/detail/completed/components/__tests__/segment-list-content.spec.tsx b/web/app/components/datasets/documents/detail/completed/components/__tests__/segment-list-content.spec.tsx index eeeeca333d..9f35b1b2eb 100644 --- a/web/app/components/datasets/documents/detail/completed/components/__tests__/segment-list-content.spec.tsx +++ b/web/app/components/datasets/documents/detail/completed/components/__tests__/segment-list-content.spec.tsx @@ -84,8 +84,6 @@ describe('GeneralModeContent', () => { embeddingAvailable: true, isLoadingSegmentList: false, segments: [{ id: 'seg-1' }, { id: 'seg-2' }] as SegmentDetailModel[], - selectedSegmentIds: [], - onSelected: vi.fn(), onChangeSwitch: vi.fn(), onDelete: vi.fn(), onClickCard: vi.fn(), diff --git a/web/app/components/datasets/documents/detail/completed/components/menu-bar.tsx b/web/app/components/datasets/documents/detail/completed/components/menu-bar.tsx index 4d0f48f223..09a3db925c 100644 --- a/web/app/components/datasets/documents/detail/completed/components/menu-bar.tsx +++ b/web/app/components/datasets/documents/detail/completed/components/menu-bar.tsx @@ -1,5 +1,4 @@ 'use client' -import type { FC } from 'react' import { Checkbox } from '@langgenius/dify-ui/checkbox' import { cn } from '@langgenius/dify-ui/cn' import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select' @@ -16,9 +15,7 @@ type Item = { } & Record type MenuBarProps = { - isAllSelected: boolean - isSomeSelected: boolean - onSelectedAll: () => void + hasSelectableSegments: boolean isLoading: boolean totalText: string statusList: Item[] @@ -30,10 +27,8 @@ type MenuBarProps = { toggleCollapsed: () => void } -const MenuBar: FC = ({ - isAllSelected, - isSomeSelected, - onSelectedAll, +function MenuBar({ + hasSelectableSegments, isLoading, totalText, statusList, @@ -43,20 +38,24 @@ const MenuBar: FC = ({ onInputChange, isCollapsed, toggleCollapsed, -}) => { +}: MenuBarProps) { const { t } = useTranslation() const selectedStatus = statusList.find(item => item.value === selectDefaultValue) ?? null return (
- onSelectedAll()} - disabled={isLoading} - /> + {hasSelectableSegments + ? ( + + ) + : ( + + )}
{totalText}