fix(web): gate zone-records fetch to no-template case; wait for domains load before branching
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,12 +130,12 @@ export function useCheckDomain(id: string, enabled = true) {
|
|||||||
enabled: !!project && !!id && enabled,
|
enabled: !!project && !!id && enabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function useZoneRecords(id: string) {
|
export function useZoneRecords(id: string, enabled = true) {
|
||||||
const { project } = useAuth()
|
const { project } = useAuth()
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["zoneRecords", project?.id, id],
|
queryKey: ["zoneRecords", project?.id, id],
|
||||||
queryFn: () => api.zoneRecords(project!.id, id),
|
queryFn: () => api.zoneRecords(project!.id, id),
|
||||||
enabled: !!project && !!id,
|
enabled: !!project && !!id && enabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
export function useCreateTemplateFromZone() {
|
export function useCreateTemplateFromZone() {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ test("apply sends applyPrunes=false by default, true only after opting in", asyn
|
|||||||
readOnly: [], inSyncCount: 0,
|
readOnly: [], inSyncCount: 0,
|
||||||
})
|
})
|
||||||
const applySpy = vi.spyOn(api, "applyDomain").mockResolvedValue({ updates: [], prunes: [], readOnly: [], inSyncCount: 0 })
|
const applySpy = vi.spyOn(api, "applyDomain").mockResolvedValue({ updates: [], prunes: [], readOnly: [], inSyncCount: 0 })
|
||||||
|
const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
renderPage()
|
renderPage()
|
||||||
|
|
||||||
@@ -63,6 +64,32 @@ test("apply sends applyPrunes=false by default, true only after opting in", asyn
|
|||||||
await user.click(screen.getByRole("button", { name: /apply/i }))
|
await user.click(screen.getByRole("button", { name: /apply/i }))
|
||||||
await waitFor(() => expect(applySpy).toHaveBeenCalledTimes(2))
|
await waitFor(() => expect(applySpy).toHaveBeenCalledTimes(2))
|
||||||
expect(applySpy.mock.calls[1]).toEqual([PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: true }])
|
expect(applySpy.mock.calls[1]).toEqual([PROJECT_ID, "d1", { applyUpdates: true, applyPrunes: true }])
|
||||||
|
|
||||||
|
// домен с шаблоном: записи зоны не нужны для диффа — запрос не должен уходить к провайдеру
|
||||||
|
expect(zoneRecordsSpy).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("пока список доменов грузится — показан общий лоадер, а не баннер об отсутствии шаблона", async () => {
|
||||||
|
let resolveListDomains: (domains: Domain[]) => void
|
||||||
|
vi.spyOn(api, "listDomains").mockReturnValue(
|
||||||
|
new Promise((resolve) => {
|
||||||
|
resolveListDomains = resolve
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const checkSpy = vi.spyOn(api, "checkDomain").mockResolvedValue({
|
||||||
|
updates: [], prunes: [], readOnly: [], inSyncCount: 0,
|
||||||
|
})
|
||||||
|
const zoneRecordsSpy = vi.spyOn(api, "zoneRecords")
|
||||||
|
renderPage()
|
||||||
|
|
||||||
|
expect(await screen.findByText(/загрузка/i)).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText(/шаблон не привязан/i)).not.toBeInTheDocument()
|
||||||
|
expect(checkSpy).not.toHaveBeenCalled()
|
||||||
|
expect(zoneRecordsSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
resolveListDomains!([domainWithTemplate])
|
||||||
|
|
||||||
|
expect(await screen.findByRole("button", { name: /apply/i })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("домен без шаблона показывает записи зоны и не вызывает check", async () => {
|
test("домен без шаблона показывает записи зоны и не вызывает check", async () => {
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ export function DomainDiffPage() {
|
|||||||
|
|
||||||
const check = useCheckDomain(id, hasTemplate)
|
const check = useCheckDomain(id, hasTemplate)
|
||||||
const apply = useApplyDomain(id)
|
const apply = useApplyDomain(id)
|
||||||
const zoneRecords = useZoneRecords(id)
|
// Пока список доменов не загружен, hasTemplate недостоверно (false по
|
||||||
|
// умолчанию из-за domain === undefined) — не дёргаем provider-запрос
|
||||||
|
// записей зоны, пока не будет точно известно, что шаблона нет.
|
||||||
|
const zoneRecords = useZoneRecords(id, !domains.isPending && !hasTemplate)
|
||||||
const createTemplateFromZone = useCreateTemplateFromZone()
|
const createTemplateFromZone = useCreateTemplateFromZone()
|
||||||
const [applyPrunes, setApplyPrunes] = useState(false)
|
const [applyPrunes, setApplyPrunes] = useState(false)
|
||||||
const pruneCheckboxId = useId()
|
const pruneCheckboxId = useId()
|
||||||
@@ -50,6 +53,17 @@ export function DomainDiffPage() {
|
|||||||
createTemplateFromZone.mutate(id)
|
createTemplateFromZone.mutate(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (domains.isPending) {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto flex max-w-3xl flex-col gap-6 px-6 py-8">
|
||||||
|
<div className="flex items-center gap-2 rounded-lg border border-border bg-card px-4 py-8 text-sm text-muted-foreground">
|
||||||
|
<Loader2 className="size-4 animate-spin" strokeWidth={1.75} />
|
||||||
|
Загрузка…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex max-w-3xl flex-col gap-6 px-6 py-8">
|
<div className="mx-auto flex max-w-3xl flex-col gap-6 px-6 py-8">
|
||||||
<header className="flex flex-wrap items-end justify-between gap-4">
|
<header className="flex flex-wrap items-end justify-between gap-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user