import type { TFunction } from 'i18next' import type { ModalContextState } from '@/context/modal-context' import type { ApiBasedExtension } from '@/models/common' import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' import * as reactI18next from 'react-i18next' import { useModalContext } from '@/context/modal-context' import { deleteApiBasedExtension } from '@/service/common' import Item from './item' // Mock dependencies vi.mock('@/context/modal-context', () => ({ useModalContext: vi.fn(), })) vi.mock('@/service/common', () => ({ deleteApiBasedExtension: vi.fn(), })) describe('Item Component', () => { const mockData: ApiBasedExtension = { id: '1', name: 'Test Extension', api_endpoint: 'https://api.example.com', api_key: 'test-api-key', } const mockOnUpdate = vi.fn() const mockSetShowApiBasedExtensionModal = vi.fn() beforeEach(() => { vi.clearAllMocks() vi.mocked(useModalContext).mockReturnValue({ setShowApiBasedExtensionModal: mockSetShowApiBasedExtensionModal, } as unknown as ModalContextState) }) describe('Rendering', () => { it('should render extension data correctly', () => { // Act render() // Assert expect(screen.getByText('Test Extension')).toBeInTheDocument() expect(screen.getByText('https://api.example.com')).toBeInTheDocument() }) it('should render with minimal extension data', () => { // Arrange const minimalData: ApiBasedExtension = { id: '2' } // Act render() // Assert expect(screen.getByText('common.operation.edit')).toBeInTheDocument() expect(screen.getByText('common.operation.delete')).toBeInTheDocument() }) }) describe('Modal Interactions', () => { it('should open edit modal with correct payload when clicking edit button', () => { // Act render() fireEvent.click(screen.getByText('common.operation.edit')) // Assert expect(mockSetShowApiBasedExtensionModal).toHaveBeenCalledWith(expect.objectContaining({ payload: mockData, })) const lastCall = mockSetShowApiBasedExtensionModal.mock.calls[0][0] if (typeof lastCall === 'object' && lastCall !== null && 'onSaveCallback' in lastCall) expect(lastCall.onSaveCallback).toBeInstanceOf(Function) }) it('should execute onUpdate callback when edit modal save callback is invoked', () => { // Act render() fireEvent.click(screen.getByText('common.operation.edit')) // Assert const modalCallArg = mockSetShowApiBasedExtensionModal.mock.calls[0][0] if (typeof modalCallArg === 'object' && modalCallArg !== null && 'onSaveCallback' in modalCallArg) { const onSaveCallback = modalCallArg.onSaveCallback if (onSaveCallback) { onSaveCallback() expect(mockOnUpdate).toHaveBeenCalledTimes(1) } } }) }) describe('Deletion', () => { it('should show delete confirmation dialog when clicking delete button', () => { // Act render() fireEvent.click(screen.getByText('common.operation.delete')) // Assert expect(screen.getByText(/common\.operation\.delete.*Test Extension.*\?/i)).toBeInTheDocument() }) it('should call delete API and triggers onUpdate when confirming deletion', async () => { // Arrange vi.mocked(deleteApiBasedExtension).mockResolvedValue({ result: 'success' }) render() // Act fireEvent.click(screen.getByText('common.operation.delete')) const dialog = screen.getByTestId('confirm-overlay') const confirmButton = within(dialog).getByText('common.operation.delete') fireEvent.click(confirmButton) // Assert await waitFor(() => { expect(deleteApiBasedExtension).toHaveBeenCalledWith('/api-based-extension/1') expect(mockOnUpdate).toHaveBeenCalledTimes(1) }) }) it('should hide delete confirmation dialog after successful deletion', async () => { // Arrange vi.mocked(deleteApiBasedExtension).mockResolvedValue({ result: 'success' }) render() // Act fireEvent.click(screen.getByText('common.operation.delete')) const dialog = screen.getByTestId('confirm-overlay') const confirmButton = within(dialog).getByText('common.operation.delete') fireEvent.click(confirmButton) // Assert await waitFor(() => { expect(screen.queryByText(/common\.operation\.delete.*Test Extension.*\?/i)).not.toBeInTheDocument() }) }) it('should close delete confirmation when clicking cancel button', () => { // Act render() fireEvent.click(screen.getByText('common.operation.delete')) fireEvent.click(screen.getByText('common.operation.cancel')) // Assert expect(screen.queryByText(/common\.operation\.delete.*Test Extension.*\?/i)).not.toBeInTheDocument() }) it('should not call delete API when canceling deletion', () => { // Act render() fireEvent.click(screen.getByText('common.operation.delete')) fireEvent.click(screen.getByText('common.operation.cancel')) // Assert expect(deleteApiBasedExtension).not.toHaveBeenCalled() expect(mockOnUpdate).not.toHaveBeenCalled() }) }) describe('Edge Cases', () => { it('should still show confirmation modal when operation.delete translation is missing', () => { // Arrange const useTranslationSpy = vi.spyOn(reactI18next, 'useTranslation') const originalValue = useTranslationSpy.getMockImplementation()?.() || { t: (key: string) => key, i18n: { language: 'en', changeLanguage: vi.fn() }, } useTranslationSpy.mockReturnValue({ ...originalValue, t: vi.fn().mockImplementation((key: string) => { if (key === 'operation.delete') return '' return key }) as unknown as TFunction, } as unknown as ReturnType) // Act render() const allButtons = screen.getAllByRole('button') const editBtn = screen.getByText('operation.edit') const deleteBtn = allButtons.find(btn => btn !== editBtn) if (deleteBtn) fireEvent.click(deleteBtn) // Assert expect(screen.getByText(/.*Test Extension.*\?/i)).toBeInTheDocument() useTranslationSpy.mockRestore() }) }) })