Sweeping the other views for the FN-8606 typing bug found no further breakage, but it did expose why the bug shipped: almost all field coverage uses fireEvent.change, which sets a value in one shot on a node it already holds and never needs the input to stay mounted. A remount is invisible to it. Adds expectStableTyping (types character by character via userEvent, then asserts DOM node identity, accumulated value, and retained focus) and applies it to the uncovered surfaces: the FN-8606-migrated AddNode, ConnectNode, NodeDetail, Scripts, and WorkflowAddStep modals, plus the board's QuickEntryBox composer and SubtaskBreakdownModal title editing. All pass — this is a detection floor, not a fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
4.1 KiB
TypeScript
94 lines
4.1 KiB
TypeScript
import { render, screen, cleanup } from "@testing-library/react";
|
|
import { describe, expect, it, vi, afterEach } from "vitest";
|
|
import { MessageSquare, Repeat } from "lucide-react";
|
|
import type { WorkflowDefinition, WorkflowStepTemplate } from "@fusion/core";
|
|
import { WorkflowAddStepModal, type AddStepPaletteEntry } from "../WorkflowAddStepModal";
|
|
import { assertModalGeometryRecoveryAndSheetContracts, assertRenderedModalTouchGeometry } from "./floatingWindowMigration.test-helpers";
|
|
import { expectStableTyping } from "./typingStability.test-helpers";
|
|
|
|
/*
|
|
FNXC:WorkflowSimpleView 2026-07-12-14:30:
|
|
PR #2006 review coverage: when the add-step dialog targets an edge INSIDE a
|
|
container (disallowContainers), it must hide container palette kinds,
|
|
fragments (top-level subgraphs cannot splice into a template-child edge),
|
|
and the "as optional group" template variant — while keeping plain step
|
|
templates, which materialize a single sibling-safe node.
|
|
*/
|
|
|
|
const palette: AddStepPaletteEntry[] = [
|
|
{ kind: "prompt", label: "Prompt", icon: MessageSquare },
|
|
{ kind: "loop", label: "Loop", icon: Repeat },
|
|
];
|
|
|
|
const fragment = {
|
|
id: "WF-FRAG",
|
|
kind: "fragment",
|
|
name: "Lint fragment",
|
|
description: "",
|
|
ir: { version: "v1", name: "Lint fragment", nodes: [], edges: [] },
|
|
layout: {},
|
|
createdAt: "2026-06-03T00:00:00.000Z",
|
|
updatedAt: "2026-06-03T00:00:00.000Z",
|
|
} as WorkflowDefinition;
|
|
|
|
const stepTemplate = { id: "tpl-1", name: "Security review" } as WorkflowStepTemplate;
|
|
|
|
function renderModal(disallowContainers: boolean) {
|
|
return render(
|
|
<WorkflowAddStepModal
|
|
open
|
|
onClose={() => {}}
|
|
palette={palette}
|
|
disallowContainers={disallowContainers}
|
|
fragments={[fragment]}
|
|
stepTemplates={[stepTemplate]}
|
|
pluginTemplates={[]}
|
|
onPickPalette={vi.fn()}
|
|
onPickFragment={vi.fn()}
|
|
onPickStepTemplate={vi.fn()}
|
|
onPickStepTemplateAsOptionalGroup={vi.fn()}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
describe("WorkflowAddStepModal", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
|
|
/*
|
|
FNXC:TypingStability 2026-07-26-22:15:
|
|
Per-character typing guard. The FN-8606 floating-window migration made Planning Mode and Settings
|
|
untypable and no test noticed, because field coverage here uses fireEvent.change, which never needs
|
|
the input to stay mounted. This asserts the field keeps its DOM node, value, and focus while typed.
|
|
*/
|
|
it("keeps the step search field mounted and focused while typing", async () => {
|
|
renderModal(false);
|
|
const field = screen.getByPlaceholderText("Search steps and templates…") as HTMLInputElement;
|
|
await expectStableTyping(field, "sec", () => screen.getByPlaceholderText("Search steps and templates…"));
|
|
});
|
|
|
|
it("offers containers, fragments, and optional-group inserts for top-level targets", () => {
|
|
renderModal(false);
|
|
expect(screen.getByTestId("wf-add-step-loop-loop")).toBeInTheDocument();
|
|
expect(screen.getByTestId("wf-add-step-fragment-WF-FRAG")).toBeInTheDocument();
|
|
expect(screen.getByTestId("wf-add-step-tpl-tpl-1")).toBeInTheDocument();
|
|
expect(screen.getByTestId("wf-add-step-tpl-tpl-1-optional-group")).toBeInTheDocument();
|
|
});
|
|
|
|
it("uses the real workflow dialog header for touch geometry", () => {
|
|
renderModal(false);
|
|
assertRenderedModalTouchGeometry("workflow-add-step", screen.getByRole("heading", { name: "Add a step" }).closest(".wf-add-step-header") as HTMLElement);
|
|
assertModalGeometryRecoveryAndSheetContracts("workflow-add-step", () => renderModal(false));
|
|
});
|
|
|
|
it("hides containers, fragments, and optional-group inserts for container-internal targets", () => {
|
|
renderModal(true);
|
|
expect(screen.getByTestId("wf-add-step-prompt-prompt")).toBeInTheDocument();
|
|
expect(screen.queryByTestId("wf-add-step-loop-loop")).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId("wf-add-step-fragment-WF-FRAG")).not.toBeInTheDocument();
|
|
// Plain step templates remain — they insert a single sibling-safe node.
|
|
expect(screen.getByTestId("wf-add-step-tpl-tpl-1")).toBeInTheDocument();
|
|
expect(screen.queryByTestId("wf-add-step-tpl-tpl-1-optional-group")).not.toBeInTheDocument();
|
|
});
|
|
});
|