fix(web): валидация записей шаблона — пустые values не уходят в API, ошибки видимы
This commit is contained in:
@@ -121,6 +121,44 @@ test("ошибка создания шаблона отображается по
|
||||
expect(await screen.findByRole("alert")).toHaveTextContent("Не удалось создать шаблон")
|
||||
})
|
||||
|
||||
test("запись с нетронутым (пустым) values не уходит в api.createTemplate, показывается ошибка", async () => {
|
||||
const createSpy = vi.spyOn(api, "createTemplate").mockClear()
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
|
||||
await screen.findByText("Standard")
|
||||
|
||||
await user.type(screen.getByLabelText(/имя шаблона/i), "New")
|
||||
await user.click(screen.getByRole("button", { name: /добавить запись/i }))
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/имя записи 1/i), { target: { value: "www" } })
|
||||
// значения записи намеренно не заполняются — остаётся дефолтная пустая строка
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /сохранить шаблон/i }))
|
||||
|
||||
expect(await screen.findByRole("alert")).toHaveTextContent(/заполните имя и значения/i)
|
||||
expect(createSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("сабмит с пустым именем записи показывает ошибку и не вызывает api.createTemplate", async () => {
|
||||
const createSpy = vi.spyOn(api, "createTemplate").mockClear()
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
|
||||
await screen.findByText("Standard")
|
||||
|
||||
await user.type(screen.getByLabelText(/имя шаблона/i), "New")
|
||||
await user.click(screen.getByRole("button", { name: /добавить запись/i }))
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/значения записи 1/i), { target: { value: "1.1.1.1" } })
|
||||
// имя записи намеренно оставлено пустым
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /сохранить шаблон/i }))
|
||||
|
||||
expect(await screen.findByRole("alert")).toHaveTextContent(/заполните имя и значения/i)
|
||||
expect(createSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("пустое состояние при отсутствии шаблонов", async () => {
|
||||
vi.spyOn(api, "listTemplates").mockResolvedValue([])
|
||||
renderPage()
|
||||
|
||||
@@ -29,7 +29,9 @@ const recordSchema = z.object({
|
||||
type: z.string().min(1),
|
||||
name: z.string().min(1, "Укажите имя записи"),
|
||||
ttl: z.number().int("TTL — целое число").nonnegative("TTL не может быть отрицательным"),
|
||||
values: z.array(z.string()),
|
||||
values: z
|
||||
.array(z.string().trim().min(1, "Значение не может быть пустым"))
|
||||
.min(1, "Добавьте хотя бы одно значение"),
|
||||
})
|
||||
|
||||
const templateFormSchema = z.object({
|
||||
@@ -41,6 +43,15 @@ type TemplateForm = z.infer<typeof templateFormSchema>
|
||||
|
||||
const EMPTY_FORM: TemplateForm = { name: "", records: [] }
|
||||
|
||||
function sanitizeRecords(records: TemplateForm["records"]) {
|
||||
return records
|
||||
.map((record) => ({
|
||||
...record,
|
||||
values: record.values.map((v) => v.trim()).filter(Boolean),
|
||||
}))
|
||||
.filter((record) => record.values.length > 0)
|
||||
}
|
||||
|
||||
export function TemplatesPage() {
|
||||
const templates = useTemplates()
|
||||
const createTemplate = useCreateTemplate()
|
||||
@@ -73,9 +84,10 @@ export function TemplatesPage() {
|
||||
}
|
||||
|
||||
function onSubmit(values: TemplateForm) {
|
||||
const input = { ...values, records: sanitizeRecords(values.records) }
|
||||
if (editingId) {
|
||||
updateTemplate.mutate(
|
||||
{ id: editingId, input: values },
|
||||
{ id: editingId, input },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setEditingId(null)
|
||||
@@ -84,7 +96,7 @@ export function TemplatesPage() {
|
||||
},
|
||||
)
|
||||
} else {
|
||||
createTemplate.mutate(values, { onSuccess: () => reset(EMPTY_FORM) })
|
||||
createTemplate.mutate(input, { onSuccess: () => reset(EMPTY_FORM) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +108,7 @@ export function TemplatesPage() {
|
||||
}
|
||||
|
||||
const saveMutation = editingId ? updateTemplate : createTemplate
|
||||
const hasFormErrors = !!errors.name || !!errors.records
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex max-w-4xl flex-col gap-6 px-6 py-8">
|
||||
@@ -147,10 +160,16 @@ export function TemplatesPage() {
|
||||
</FieldSet>
|
||||
|
||||
<div className="flex items-center justify-between gap-3 border-t border-border pt-3">
|
||||
{saveMutation.isError && (
|
||||
{hasFormErrors ? (
|
||||
<span role="alert" className="font-dns text-xs text-destructive">
|
||||
{saveMutation.error.message}
|
||||
Заполните имя и значения всех записей
|
||||
</span>
|
||||
) : (
|
||||
saveMutation.isError && (
|
||||
<span role="alert" className="font-dns text-xs text-destructive">
|
||||
{saveMutation.error.message}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{editingId && (
|
||||
|
||||
Reference in New Issue
Block a user