Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df3b7a8cb0 | |||
| 426af29180 |
@@ -43,13 +43,18 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
const [form, setForm] = useState<Partial<NodeData>>({ ...DEFAULT_DATA, ...initial })
|
const [form, setForm] = useState<Partial<NodeData>>({ ...DEFAULT_DATA, ...initial })
|
||||||
const [iconSearch, setIconSearch] = useState('')
|
const [iconSearch, setIconSearch] = useState('')
|
||||||
const [iconPickerOpen, setIconPickerOpen] = useState(false)
|
const [iconPickerOpen, setIconPickerOpen] = useState(false)
|
||||||
|
const [labelError, setLabelError] = useState(false)
|
||||||
|
|
||||||
const set = (key: keyof NodeData, value: unknown) =>
|
const set = (key: keyof NodeData, value: unknown) =>
|
||||||
setForm((f) => ({ ...f, [key]: value }))
|
setForm((f) => ({ ...f, [key]: value }))
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!form.label?.trim()) return
|
if (!form.label?.trim()) {
|
||||||
|
setLabelError(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setLabelError(false)
|
||||||
onSubmit(form)
|
onSubmit(form)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
@@ -167,11 +172,11 @@ export function NodeModal({ open, onClose, onSubmit, initial, title = 'Add Node'
|
|||||||
<Label className="text-xs text-muted-foreground">Label *</Label>
|
<Label className="text-xs text-muted-foreground">Label *</Label>
|
||||||
<Input
|
<Input
|
||||||
value={form.label ?? ''}
|
value={form.label ?? ''}
|
||||||
onChange={(e) => set('label', e.target.value)}
|
onChange={(e) => { set('label', e.target.value); if (labelError) setLabelError(false) }}
|
||||||
placeholder="My Server"
|
placeholder="My Server"
|
||||||
className="bg-[#21262d] border-[#30363d] text-sm h-8"
|
className={`bg-[#21262d] text-sm h-8 ${labelError ? 'border-[#f85149] focus-visible:ring-[#f85149]' : 'border-[#30363d]'}`}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
|
{labelError && <p className="text-[11px] text-[#f85149]">Label is required</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Hostname */}
|
{/* Hostname */}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest'
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import { NodeModal } from '../NodeModal'
|
||||||
|
|
||||||
|
describe('NodeModal', () => {
|
||||||
|
it('renders nothing when closed', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<NodeModal open={false} onClose={vi.fn()} onSubmit={vi.fn()} />
|
||||||
|
)
|
||||||
|
expect(container.querySelector('[role="dialog"]')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders form fields when open', () => {
|
||||||
|
render(<NodeModal open onClose={vi.fn()} onSubmit={vi.fn()} />)
|
||||||
|
expect(screen.getByPlaceholderText('My Server')).toBeDefined()
|
||||||
|
expect(screen.getByText('Add Node')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not call onSubmit when label is empty and shows error', () => {
|
||||||
|
const onSubmit = vi.fn()
|
||||||
|
render(<NodeModal open onClose={vi.fn()} onSubmit={onSubmit} />)
|
||||||
|
fireEvent.click(screen.getByText('Add'))
|
||||||
|
expect(onSubmit).not.toHaveBeenCalled()
|
||||||
|
expect(screen.getByText('Label is required')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onSubmit with form data when label is filled', () => {
|
||||||
|
const onSubmit = vi.fn()
|
||||||
|
const onClose = vi.fn()
|
||||||
|
render(<NodeModal open onClose={onClose} onSubmit={onSubmit} />)
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('My Server'), { target: { value: 'My NAS' } })
|
||||||
|
fireEvent.click(screen.getByText('Add'))
|
||||||
|
expect(onSubmit).toHaveBeenCalledOnce()
|
||||||
|
expect(onSubmit.mock.calls[0][0].label).toBe('My NAS')
|
||||||
|
expect(onClose).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears label error when user starts typing', () => {
|
||||||
|
render(<NodeModal open onClose={vi.fn()} onSubmit={vi.fn()} />)
|
||||||
|
fireEvent.click(screen.getByText('Add'))
|
||||||
|
expect(screen.getByText('Label is required')).toBeDefined()
|
||||||
|
fireEvent.change(screen.getByPlaceholderText('My Server'), { target: { value: 'x' } })
|
||||||
|
expect(screen.queryByText('Label is required')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pre-fills form from initial prop', () => {
|
||||||
|
render(
|
||||||
|
<NodeModal open onClose={vi.fn()} onSubmit={vi.fn()} initial={{ label: 'Pre-filled', ip: '10.0.0.1' }} />
|
||||||
|
)
|
||||||
|
const input = screen.getByPlaceholderText('My Server') as HTMLInputElement
|
||||||
|
expect(input.value).toBe('Pre-filled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows Save button text when title is Edit Node', () => {
|
||||||
|
render(<NodeModal open onClose={vi.fn()} onSubmit={vi.fn()} title="Edit Node" />)
|
||||||
|
expect(screen.getByText('Save')).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls onClose when Cancel is clicked', () => {
|
||||||
|
const onClose = vi.fn()
|
||||||
|
render(<NodeModal open onClose={onClose} onSubmit={vi.fn()} />)
|
||||||
|
fireEvent.click(screen.getByText('Cancel'))
|
||||||
|
expect(onClose).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user