From e283e5f22af3a686853f3e1e3fcccaa8486b1349 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Sun, 5 Jul 2026 15:26:46 +0700 Subject: [PATCH] fix: document apply ordering invariant; visible indeterminate checkbox + test Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3 --- internal/provider/provider.go | 6 ++++++ web/src/components/DiffView.test.tsx | 22 ++++++++++++++++++++++ web/src/components/ui/checkbox.tsx | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 28287a8..8723e3d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -25,6 +25,12 @@ type Provider interface { Name() string ListZones(ctx context.Context, creds Credentials) ([]Zone, error) GetRecords(ctx context.Context, creds Credentials, zoneID string) ([]model.Record, error) + // ApplyChanges MUST apply cs.Diffs in the order they are given and must + // not reorder or group them by Kind. The caller (service.Apply) + // deliberately places Delete diffs before Add/Update diffs, because some + // providers (e.g. Selectel) reject creating a CNAME on a name where a + // conflicting A record still exists. Implementations should apply diffs + // sequentially in the given order rather than batching by kind. ApplyChanges(ctx context.Context, creds Credentials, zoneID string, cs diff.Changeset) error // Validate checks the credentials are usable (e.g. a trial auth), so a // bad account is rejected at creation time rather than at first import. diff --git a/web/src/components/DiffView.test.tsx b/web/src/components/DiffView.test.tsx index b5e5a6a..88b4a99 100644 --- a/web/src/components/DiffView.test.tsx +++ b/web/src/components/DiffView.test.tsx @@ -131,3 +131,25 @@ test("select-all header checkbox calls onToggleAllUpdates(true) when clicked whi await user.click(screen.getByRole("checkbox", { name: /выбрать все.*updates/i })) expect(onToggleAllUpdates).toHaveBeenCalledWith(true) }) + +test("select-all header checkbox is indeterminate when only some update rows are selected", () => { + const csWithMultipleUpdates: ChangesetResponse = { + updates: [ + { key: "A www.example.com.", kind: "update", type: "A", name: "www.example.com.", desired: ["1.1.1.1"], actual: ["9.9.9.9"], readOnly: false }, + { key: "A api.example.com.", kind: "update", type: "A", name: "api.example.com.", desired: ["1.1.1.2"], actual: ["9.9.9.8"], readOnly: false }, + { key: "A cdn.example.com.", kind: "update", type: "A", name: "cdn.example.com.", desired: ["1.1.1.3"], actual: ["9.9.9.7"], readOnly: false }, + ], + prunes: [], + readOnly: [], + inSyncCount: 0, + } + renderDiff({ + changeset: csWithMultipleUpdates, + // Partial selection: one of three keys — neither all nor none — is what + // must drive the header checkbox into the indeterminate ("mixed") state. + selectedUpdates: new Set(["A www.example.com."]), + }) + + const selectAll = screen.getByRole("checkbox", { name: /выбрать все.*updates/i }) + expect(selectAll).toHaveAttribute("aria-checked", "mixed") +}) diff --git a/web/src/components/ui/checkbox.tsx b/web/src/components/ui/checkbox.tsx index 824b50f..edcba69 100644 --- a/web/src/components/ui/checkbox.tsx +++ b/web/src/components/ui/checkbox.tsx @@ -8,7 +8,7 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {