diff --git a/apps/client/src/widgets/note_wrapper.ts b/apps/client/src/widgets/note_wrapper.ts index ccaa5614d91..b23c0417563 100644 --- a/apps/client/src/widgets/note_wrapper.ts +++ b/apps/client/src/widgets/note_wrapper.ts @@ -19,25 +19,21 @@ export default class NoteWrapperWidget extends FlexContainer { setNoteContextEvent({ noteContext }: EventData<"setNoteContext">) { this.noteContext = noteContext; - this.refresh(); } noteSwitchedAndActivatedEvent({ noteContext }: EventData<"setNoteContext">) { this.noteContext = noteContext; - this.refresh(); } noteSwitchedEvent({ noteContext }: EventData<"setNoteContext">) { this.noteContext = noteContext; - this.refresh(); } activeContextChangedEvent({ noteContext }: EventData<"setNoteContext">) { this.noteContext = noteContext; - this.refresh(); } 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 88703315124..d2a4336b952 100644 --- a/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx +++ b/apps/client/src/widgets/type_widgets/relation_map/RelationMap.tsx @@ -402,6 +402,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; diff --git a/apps/client/src/widgets/type_widgets/relation_map/utils.ts b/apps/client/src/widgets/type_widgets/relation_map/utils.ts index d21857a398c..fa3f8a25950 100644 --- a/apps/client/src/widgets/type_widgets/relation_map/utils.ts +++ b/apps/client/src/widgets/type_widgets/relation_map/utils.ts @@ -50,6 +50,16 @@ export function promptForRelationName(defaultValue?: string): Promise { + if (e.key === "Enter") { + $answer[0].dispatchEvent(new Event("input", { bubbles: true })); + } + }); + + $answer.on("blur", () => { + $answer[0].dispatchEvent(new Event("input", { bubbles: true })); + }); + attribute_autocomplete.initAttributeNameAutocomplete({ $el: $answer, attributeType: "relation", diff --git a/test-results/desktop-e2e-example-First-setup/trace.zip b/test-results/desktop-e2e-example-First-setup/trace.zip new file mode 100644 index 00000000000..cc5251154cf Binary files /dev/null and b/test-results/desktop-e2e-example-First-setup/trace.zip differ