From ca30530168bfe926bcc7bbf1fee20747ee756296 Mon Sep 17 00:00:00 2001 From: Renato Neto Date: Wed, 18 Mar 2026 14:57:25 +0000 Subject: [PATCH] Fix #6929: relation map ignores selected autocomplete name Previously, in the relation map, selecting an autocomplete option when specifying a relation name did not update the internal state. It incorrectly used the partially typed name instead of the selected one. This commit fixes the issue by dispatching an "input" event on 'blur' and 'Enter' keydown. This ensures that the final auto-completed value is properly synchronized and used as the relation name. --- .../relation_map/RelationMap.spec.ts | 73 +++++++++++++++++++ .../type_widgets/relation_map/RelationMap.tsx | 14 ++++ 2 files changed, 87 insertions(+) create mode 100644 apps/client/src/widgets/type_widgets/relation_map/RelationMap.spec.ts diff --git a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.spec.ts b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.spec.ts new file mode 100644 index 00000000000..7a2bd50dbbf --- /dev/null +++ b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.spec.ts @@ -0,0 +1,73 @@ +import $ from "jquery"; +import { describe, expect, it, vi } from "vitest"; + +import { setupAnswerEventHandlers } from "./RelationMap.js"; + +vi.mock("../../../services/utils.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + default: { + ...actual.default, + filterAttributeName: vi.fn((val: string) => val) + } + }; +}); + +describe("RelationMap - $answer event synchronization", () => { + + it("dispatches input event with bubbles:true when Enter is pressed", () => { + const $answer = $(""); + const dispatchSpy = vi.spyOn($answer[0], "dispatchEvent"); + + setupAnswerEventHandlers($answer); + $answer.trigger($.Event("keydown", { key: "Enter" })); + + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ type: "input", bubbles: true }) + ); + }); + + it("dispatches input event with bubbles:true when input loses focus (blur)", () => { + const $answer = $(""); + const dispatchSpy = vi.spyOn($answer[0], "dispatchEvent"); + + setupAnswerEventHandlers($answer); + $answer.trigger("blur"); + + expect(dispatchSpy).toHaveBeenCalledWith( + expect.objectContaining({ type: "input", bubbles: true }) + ); + }); + + it("does not dispatch input event for non-Enter keys", () => { + const $answer = $(""); + const dispatchSpy = vi.spyOn($answer[0], "dispatchEvent"); + + setupAnswerEventHandlers($answer); + $answer.trigger($.Event("keydown", { key: "a" })); + $answer.trigger($.Event("keydown", { key: "Tab" })); + $answer.trigger($.Event("keydown", { key: "Escape" })); + + const inputEvents = dispatchSpy.mock.calls + .map(([e]) => e as Event) + .filter((e) => e.type === "input" && e.bubbles === true); + expect(inputEvents).toHaveLength(0); + }); + + it("input event bubbles up to parent element", () => { + const $answer = $(""); + const parent = document.createElement("div"); + parent.appendChild($answer[0]); + + const parentListener = vi.fn(); + parent.addEventListener("input", parentListener); + + setupAnswerEventHandlers($answer); + $answer.trigger($.Event("keydown", { key: "Enter" })); + + expect(parentListener).toHaveBeenCalledOnce(); + expect(parentListener.mock.calls[0][0]).toMatchObject({ type: "input", bubbles: true }); + }); + +}); diff --git a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx index 867fd44d391..d9f2dd62fbf 100644 --- a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx +++ b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx @@ -404,6 +404,18 @@ function useNoteDragging({ containerRef, mapApiRef }: { return dragProps; } +export function setupAnswerEventHandlers($answer: JQuery) { + $answer.on("keydown", (e) => { + if (e.key === "Enter") { + $answer[0].dispatchEvent(new Event("input", { bubbles: true })); + } + }); + + $answer.on("blur", () => { + $answer[0].dispatchEvent(new Event("input", { bubbles: true })); + }); +} + function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObject, jsPlumbApiRef: RefObject }) { const connectionCallback = useCallback(async (info: OnConnectionBindInfo, originalEvent: Event) => { const connection = info.connection; @@ -422,6 +434,8 @@ function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObjec return; } + setupAnswerEventHandlers($answer); + $answer.on("keyup", () => { // invalid characters are simply ignored (from user perspective they are not even entered) const attrName = utils.filterAttributeName($answer.val() as string);