fix: preserve conflicted update target
This commit is contained in:
@@ -373,6 +373,38 @@ describe("useDraftAuthoring", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("reapplies a conflicted update to its original node after selection changes", async () => {
|
||||
const initial = workspace({
|
||||
draft: {
|
||||
steps: {
|
||||
read: { use: "demo.read" },
|
||||
publish: { use: "demo.publish" },
|
||||
},
|
||||
routes: {},
|
||||
},
|
||||
});
|
||||
const conflict = workspace({ revision: 4, status: "conflict", draft: null });
|
||||
authoringClient.updateCapabilityStep
|
||||
.mockResolvedValueOnce(conflict)
|
||||
.mockResolvedValueOnce(workspace({ revision: 5 }));
|
||||
const { result } = renderHook(() => useDraftAuthoring({
|
||||
draft: initial,
|
||||
initialSelection: { kind: "node", nodeId: "read" },
|
||||
}));
|
||||
|
||||
await act(async () => result.current.updateCapability({
|
||||
...capabilityInput,
|
||||
stepId: "read",
|
||||
capabilityName: "demo.read",
|
||||
}));
|
||||
act(() => result.current.select({ kind: "node", nodeId: "publish" }));
|
||||
await act(async () => result.current.reapply());
|
||||
|
||||
expect(authoringClient.updateCapabilityStep).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ stepId: "read" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("reloads explicitly and coalesces duplicate submissions", async () => {
|
||||
const initial = workspace();
|
||||
const reloaded = workspace({ revision: 10, status: "valid" });
|
||||
|
||||
@@ -74,7 +74,11 @@ type PendingMutation = {
|
||||
|
||||
type LastSubmission =
|
||||
| { readonly kind: "add"; readonly input: CapabilityNodeFormValue }
|
||||
| { readonly kind: "update"; readonly input: CapabilityNodeFormValue }
|
||||
| {
|
||||
readonly kind: "update";
|
||||
readonly targetStepId: string;
|
||||
readonly input: CapabilityNodeFormValue;
|
||||
}
|
||||
| { readonly kind: "route"; readonly input: RouteFormValue }
|
||||
| null;
|
||||
|
||||
@@ -331,23 +335,19 @@ export const useDraftAuthoring = ({
|
||||
[runMutation],
|
||||
);
|
||||
|
||||
const updateCapability = useCallback(
|
||||
(input: CapabilityNodeFormValue): Promise<void> => {
|
||||
lastSubmissionRef.current = { kind: "update", input };
|
||||
// The form's Step id is display-only for updates; mutation targets stay
|
||||
// bound to the node selected when the operator opened the inspector.
|
||||
const selectedNodeId =
|
||||
currentSelectionRef.current.kind === "node"
|
||||
? currentSelectionRef.current.nodeId
|
||||
: input.stepId;
|
||||
const submitCapabilityUpdate = useCallback(
|
||||
(targetStepId: string, input: CapabilityNodeFormValue): Promise<void> => {
|
||||
// Persist the immutable mutation target with the form. A conflict may
|
||||
// outlive the current graph selection before the operator reapplies it.
|
||||
lastSubmissionRef.current = { kind: "update", targetStepId, input };
|
||||
return runMutation(
|
||||
"update",
|
||||
input,
|
||||
{ targetStepId, input },
|
||||
(client, requestDraft) =>
|
||||
client.updateCapabilityStep({
|
||||
workspaceId: requestDraft.workspaceId,
|
||||
revision: requestDraft.revision,
|
||||
stepId: selectedNodeId,
|
||||
stepId: targetStepId,
|
||||
update: {
|
||||
description: input.description,
|
||||
input: input.inputBindings,
|
||||
@@ -360,6 +360,19 @@ export const useDraftAuthoring = ({
|
||||
[runMutation],
|
||||
);
|
||||
|
||||
const updateCapability = useCallback(
|
||||
(input: CapabilityNodeFormValue): Promise<void> => {
|
||||
// The form's Step id is display-only for updates; mutation targets stay
|
||||
// bound to the node selected when the operator opened the inspector.
|
||||
const targetStepId =
|
||||
currentSelectionRef.current.kind === "node"
|
||||
? currentSelectionRef.current.nodeId
|
||||
: input.stepId;
|
||||
return submitCapabilityUpdate(targetStepId, input);
|
||||
},
|
||||
[submitCapabilityUpdate],
|
||||
);
|
||||
|
||||
const setRoute = useCallback(
|
||||
(input: RouteFormValue): Promise<void> => {
|
||||
lastSubmissionRef.current = { kind: "route", input };
|
||||
@@ -456,13 +469,24 @@ export const useDraftAuthoring = ({
|
||||
const last = lastSubmissionRef.current;
|
||||
if (last === null) return Promise.resolve();
|
||||
if (last.kind === "add") return addCapability(last.input);
|
||||
if (last.kind === "update") return updateCapability(last.input);
|
||||
if (last.kind === "update") return submitCapabilityUpdate(last.targetStepId, last.input);
|
||||
return setRoute(last.input);
|
||||
}, [addCapability, setRoute, updateCapability]);
|
||||
}, [addCapability, setRoute, submitCapabilityUpdate]);
|
||||
|
||||
const rememberCapabilityForm = useCallback(
|
||||
(kind: "add" | "update", input: CapabilityNodeFormValue): void => {
|
||||
lastSubmissionRef.current = { kind, input };
|
||||
if (kind === "add") {
|
||||
lastSubmissionRef.current = { kind, input };
|
||||
return;
|
||||
}
|
||||
const previous = lastSubmissionRef.current;
|
||||
const targetStepId =
|
||||
previous?.kind === "update"
|
||||
? previous.targetStepId
|
||||
: currentSelectionRef.current.kind === "node"
|
||||
? currentSelectionRef.current.nodeId
|
||||
: input.stepId;
|
||||
lastSubmissionRef.current = { kind, targetStepId, input };
|
||||
},
|
||||
[],
|
||||
);
|
||||
@@ -487,7 +511,10 @@ export const useDraftAuthoring = ({
|
||||
reapply,
|
||||
preservedCapabilityForm:
|
||||
lastSubmissionRef.current?.kind === "add" || lastSubmissionRef.current?.kind === "update"
|
||||
? lastSubmissionRef.current
|
||||
? {
|
||||
kind: lastSubmissionRef.current.kind,
|
||||
input: lastSubmissionRef.current.input,
|
||||
}
|
||||
: null,
|
||||
rememberCapabilityForm,
|
||||
rememberRouteForm,
|
||||
|
||||
Reference in New Issue
Block a user