fix(ImportSitesModal): improve error handling for file reading in handleFileInput

This commit is contained in:
GitHub Actions
2026-01-31 21:08:51 +00:00
parent 5d7240537f
commit 68e267846e
2 changed files with 88 additions and 4 deletions

View File

@@ -0,0 +1,84 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import ImportSitesModal from './ImportSitesModal'
import { vi } from 'vitest'
// Mock the upload API used by the component
const mockUpload = vi.fn()
vi.mock('../api/import', () => ({
uploadCaddyfilesMulti: (...args: unknown[]) => mockUpload(...(args as any[])),
}))
describe('ImportSitesModal', () => {
beforeEach(() => {
mockUpload.mockReset()
})
test('renders modal, add and remove sites, and edits textarea', () => {
const onClose = vi.fn()
render(<ImportSitesModal visible={true} onClose={onClose} />)
// modal container is present
expect(screen.getByTestId('multi-site-modal')).toBeInTheDocument()
// initially one textarea
const areas = screen.getAllByRole('textbox')
expect(areas.length).toBeGreaterThanOrEqual(1)
// add a site -> two textareas
fireEvent.click(screen.getByText('+ Add site'))
expect(screen.getAllByRole('textbox').length).toBe(areas.length + 1)
// remove the second site
const removeBtn = screen.getByText('Remove')
fireEvent.click(removeBtn)
expect(screen.getAllByRole('textbox').length).toBe(areas.length)
// type into textarea
const ta = screen.getAllByRole('textbox')[0]
fireEvent.change(ta, { target: { value: 'example.com { reverse_proxy 127.0.0.1:8080 }' } })
expect((ta as HTMLTextAreaElement).value).toContain('example.com')
})
test('reads multiple files via hidden input and submits successfully', async () => {
const onClose = vi.fn()
const onUploaded = vi.fn()
mockUpload.mockResolvedValueOnce(undefined)
const { container } = render(<ImportSitesModal visible={true} onClose={onClose} onUploaded={onUploaded} />)
// find the hidden file input
const input: HTMLInputElement | null = container.querySelector('input[type="file"]')
expect(input).toBeTruthy()
// create two files
const f1 = new File(['site1'], 'site1.caddy', { type: 'text/plain' })
const f2 = new File(['site2'], 'site2.caddy', { type: 'text/plain' })
// fire change event with files
fireEvent.change(input!, { target: { files: [f1, f2] } })
// after input, two textareas should appear
await waitFor(() => expect(screen.getAllByRole('textbox').length).toBe(2))
// submit
fireEvent.click(screen.getByText('Parse and Review'))
await waitFor(() => expect(mockUpload).toHaveBeenCalled())
expect(mockUpload).toHaveBeenCalledWith(['site1', 'site2'])
expect(onUploaded).toHaveBeenCalled()
expect(onClose).toHaveBeenCalled()
})
test('displays error when upload fails', async () => {
const onClose = vi.fn()
mockUpload.mockRejectedValueOnce(new Error('upload-failed'))
render(<ImportSitesModal visible={true} onClose={onClose} />)
// click submit with default empty site
fireEvent.click(screen.getByText('Parse and Review'))
// error message appears
await waitFor(() => expect(screen.getByText(/upload-failed|Upload failed/i)).toBeInTheDocument())
})
})

View File

@@ -29,10 +29,10 @@ export default function ImportSitesModal({ visible, onClose, onUploaded }: Props
try {
const text = await files[i].text()
newSites.push(text)
} catch (err) {
// ignore read errors for individual files
newSites.push('')
}
} catch (_err) {
// ignore read errors for individual files
newSites.push('')
}
}
if (newSites.length > 0) setSites(newSites)
}