import { act } from "@testing-library/react"; import type { Ace } from "ace-builds"; /** * Reach the live Ace editor a component mounted, and drive it. * * Typing is deliberately **not** simulated. Ace reads keystrokes through a 1px * offscreen textarea plus selection and composition state that happy-dom does * not implement, so `userEvent.type` reaches the element and produces no edit * at all — a test written that way asserts nothing while looking like it * asserts a lot. Real keyboard behaviour (auto-closing a brace on Enter, and * the draft surviving invalid text) is covered by the `JsonObjectInput` * Storybook play functions, which run in Chromium. * * Ace hangs the instance off its container element as `env.editor`; that is the * supported way back to it from the DOM. */ export function getAceEditor(container: ParentNode = document): Ace.Editor { const node = container.querySelector(".ace_editor"); const editor = ( node as (HTMLElement & { env?: { editor?: Ace.Editor } }) | null )?.env?.editor; if (!editor) throw new Error("Ace editor not mounted"); return editor; } /** The document the editor currently holds. */ export function getAceText(container?: ParentNode): string { return getAceEditor(container).getValue(); } /** * Replace the document the way a select-all-and-retype would, and let React * flush. * * Async because `JsonObjectInput` coalesces the paired remove/insert events * Ace fires for a replace; an `await act` is what drains that microtask. */ export async function setAceText( text: string, container?: ParentNode, ): Promise { await act(async () => { // `1` parks the cursor at the end, matching where typing would leave it. getAceEditor(container).setValue(text, 1); }); } /** * Matcher for an Ace-labelled control. * * Ace composes its text input's `aria-label` as `"