dify/web/app/components/datasets/common/image-uploader/store.spec.tsx

306 lines
8.4 KiB
TypeScript

import type { FileEntity } from './types'
import { act, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import {
createFileStore,
FileContextProvider,
useFileStore,
useFileStoreWithSelector,
} from './store'
const createMockFile = (id: string): FileEntity => ({
id,
name: `file-${id}.png`,
size: 1024,
extension: 'png',
mimeType: 'image/png',
progress: 0,
})
describe('image-uploader store', () => {
describe('createFileStore', () => {
it('should create store with empty array by default', () => {
const store = createFileStore()
expect(store.getState().files).toEqual([])
})
it('should create store with initial value', () => {
const initialFiles = [createMockFile('1'), createMockFile('2')]
const store = createFileStore(initialFiles)
expect(store.getState().files).toHaveLength(2)
})
it('should create copy of initial value', () => {
const initialFiles = [createMockFile('1')]
const store = createFileStore(initialFiles)
store.getState().files.push(createMockFile('2'))
expect(initialFiles).toHaveLength(1)
})
it('should update files with setFiles', () => {
const store = createFileStore()
const newFiles = [createMockFile('1'), createMockFile('2')]
act(() => {
store.getState().setFiles(newFiles)
})
expect(store.getState().files).toEqual(newFiles)
})
it('should call onChange when setFiles is called', () => {
const onChange = vi.fn()
const store = createFileStore([], onChange)
const newFiles = [createMockFile('1')]
act(() => {
store.getState().setFiles(newFiles)
})
expect(onChange).toHaveBeenCalledWith(newFiles)
})
it('should not throw when onChange is not provided', () => {
const store = createFileStore([])
const newFiles = [createMockFile('1')]
expect(() => {
act(() => {
store.getState().setFiles(newFiles)
})
}).not.toThrow()
})
it('should handle undefined initial value', () => {
const store = createFileStore(undefined)
expect(store.getState().files).toEqual([])
})
it('should handle null-like falsy value with empty array fallback', () => {
// Test the ternary: value ? [...value] : []
const store = createFileStore(null as unknown as FileEntity[])
expect(store.getState().files).toEqual([])
})
it('should handle empty array as initial value', () => {
const store = createFileStore([])
expect(store.getState().files).toEqual([])
})
})
describe('FileContextProvider', () => {
it('should render children', () => {
render(
<FileContextProvider>
<div>Test Child</div>
</FileContextProvider>,
)
expect(screen.getByText('Test Child')).toBeInTheDocument()
})
it('should provide store to children', () => {
const TestComponent = () => {
const store = useFileStore()
// useFileStore returns a store that's truthy by design
return <div data-testid="store-exists">{store !== null ? 'yes' : 'no'}</div>
}
render(
<FileContextProvider>
<TestComponent />
</FileContextProvider>,
)
expect(screen.getByTestId('store-exists')).toHaveTextContent('yes')
})
it('should initialize store with value prop', () => {
const initialFiles = [createMockFile('1')]
const TestComponent = () => {
const store = useFileStore()
return <div data-testid="file-count">{store.getState().files.length}</div>
}
render(
<FileContextProvider value={initialFiles}>
<TestComponent />
</FileContextProvider>,
)
expect(screen.getByTestId('file-count')).toHaveTextContent('1')
})
it('should call onChange when files change', () => {
const onChange = vi.fn()
const newFiles = [createMockFile('1')]
const TestComponent = () => {
const store = useFileStore()
return (
<button onClick={() => store.getState().setFiles(newFiles)}>
Set Files
</button>
)
}
render(
<FileContextProvider onChange={onChange}>
<TestComponent />
</FileContextProvider>,
)
act(() => {
screen.getByRole('button').click()
})
expect(onChange).toHaveBeenCalledWith(newFiles)
})
it('should reuse existing store on re-render (storeRef.current already exists)', () => {
const initialFiles = [createMockFile('1')]
let renderCount = 0
const TestComponent = () => {
const store = useFileStore()
renderCount++
return (
<div>
<span data-testid="file-count">{store.getState().files.length}</span>
<span data-testid="render-count">{renderCount}</span>
</div>
)
}
const { rerender } = render(
<FileContextProvider value={initialFiles}>
<TestComponent />
</FileContextProvider>,
)
expect(screen.getByTestId('file-count')).toHaveTextContent('1')
// Re-render the provider - should reuse the same store
rerender(
<FileContextProvider value={initialFiles}>
<TestComponent />
</FileContextProvider>,
)
// Store should still have the same files (store was reused)
expect(screen.getByTestId('file-count')).toHaveTextContent('1')
expect(renderCount).toBeGreaterThan(1)
})
})
describe('useFileStore', () => {
it('should return store from context', () => {
const TestComponent = () => {
const store = useFileStore()
// useFileStore returns a store that's truthy by design
return <div data-testid="result">{store !== null ? 'has store' : 'no store'}</div>
}
render(
<FileContextProvider>
<TestComponent />
</FileContextProvider>,
)
expect(screen.getByTestId('result')).toHaveTextContent('has store')
})
})
describe('useFileStoreWithSelector', () => {
it('should throw error when used outside provider', () => {
const TestComponent = () => {
try {
useFileStoreWithSelector(state => state.files)
return <div>No Error</div>
}
catch {
return <div>Error</div>
}
}
render(<TestComponent />)
expect(screen.getByText('Error')).toBeInTheDocument()
})
it('should select files from store', () => {
const initialFiles = [createMockFile('1'), createMockFile('2')]
const TestComponent = () => {
const files = useFileStoreWithSelector(state => state.files)
return <div data-testid="files-count">{files.length}</div>
}
render(
<FileContextProvider value={initialFiles}>
<TestComponent />
</FileContextProvider>,
)
expect(screen.getByTestId('files-count')).toHaveTextContent('2')
})
it('should select setFiles function from store', () => {
const onChange = vi.fn()
const TestComponent = () => {
const setFiles = useFileStoreWithSelector(state => state.setFiles)
return (
<button onClick={() => setFiles([createMockFile('new')])}>
Update
</button>
)
}
render(
<FileContextProvider onChange={onChange}>
<TestComponent />
</FileContextProvider>,
)
act(() => {
screen.getByRole('button').click()
})
expect(onChange).toHaveBeenCalled()
})
it('should re-render when selected state changes', () => {
const renderCount = { current: 0 }
const TestComponent = () => {
const files = useFileStoreWithSelector(state => state.files)
const setFiles = useFileStoreWithSelector(state => state.setFiles)
renderCount.current++
return (
<div>
<span data-testid="count">{files.length}</span>
<button onClick={() => setFiles([...files, createMockFile('new')])}>
Add
</button>
</div>
)
}
render(
<FileContextProvider>
<TestComponent />
</FileContextProvider>,
)
expect(screen.getByTestId('count')).toHaveTextContent('0')
act(() => {
screen.getByRole('button').click()
})
expect(screen.getByTestId('count')).toHaveTextContent('1')
})
})
})