feat(FN-2556): add TaskForm bottom slot for model configuration
- Add a new renderBelowModelConfiguration prop in TaskForm and render it beneath the Model Configuration section inside More options - Update TaskDetailModal to use the new slot so the Source Issue edit block appears below model settings instead of near the primary section - Adjust TaskDetailModal source-issue edit tests to expand More options before interacting with source fields - Add ordering assertions in TaskForm and TaskDetailModal tests to verify the injected block sits between Model Configuration and Workflow Steps
This commit is contained in:
@@ -1289,7 +1289,7 @@ export function TaskDetailModal({
|
|||||||
onPriorityChange={setEditPriority}
|
onPriorityChange={setEditPriority}
|
||||||
executionMode={editExecutionMode}
|
executionMode={editExecutionMode}
|
||||||
onExecutionModeChange={setEditExecutionMode}
|
onExecutionModeChange={setEditExecutionMode}
|
||||||
renderBelowPrimary={(
|
renderBelowModelConfiguration={(
|
||||||
<div className="form-group detail-source-edit-group">
|
<div className="form-group detail-source-edit-group">
|
||||||
<label>Source Issue</label>
|
<label>Source Issue</label>
|
||||||
<div className="detail-source-edit-grid">
|
<div className="detail-source-edit-grid">
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ export interface TaskFormProps {
|
|||||||
|
|
||||||
/** Optional content to render between the primary section and the "More options" toggle. */
|
/** Optional content to render between the primary section and the "More options" toggle. */
|
||||||
renderBelowPrimary?: React.ReactNode;
|
renderBelowPrimary?: React.ReactNode;
|
||||||
|
/** Optional content to render inside "More options" below Model Configuration. */
|
||||||
|
renderBelowModelConfiguration?: React.ReactNode;
|
||||||
/** When true, skip rendering the Dependencies form-group inside "More options". Use when the parent renders its own dependency UI via renderBelowPrimary. */
|
/** When true, skip rendering the Dependencies form-group inside "More options". Use when the parent renders its own dependency UI via renderBelowPrimary. */
|
||||||
hideDependencies?: boolean;
|
hideDependencies?: boolean;
|
||||||
/** When true (default), More options auto-expands when non-default advanced selections are present. */
|
/** When true (default), More options auto-expands when non-default advanced selections are present. */
|
||||||
@@ -133,6 +135,7 @@ export function TaskForm({
|
|||||||
onSubtaskBreakdown,
|
onSubtaskBreakdown,
|
||||||
onClose,
|
onClose,
|
||||||
renderBelowPrimary,
|
renderBelowPrimary,
|
||||||
|
renderBelowModelConfiguration,
|
||||||
hideDependencies,
|
hideDependencies,
|
||||||
autoExpandMoreOptionsOnSelection = true,
|
autoExpandMoreOptionsOnSelection = true,
|
||||||
reviewLevel,
|
reviewLevel,
|
||||||
@@ -1052,6 +1055,8 @@ export function TaskForm({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{renderBelowModelConfiguration}
|
||||||
|
|
||||||
{/* Workflow Steps */}
|
{/* Workflow Steps */}
|
||||||
<div className="form-group" data-testid="workflow-steps-section">
|
<div className="form-group" data-testid="workflow-steps-section">
|
||||||
<label>Workflow Steps</label>
|
<label>Workflow Steps</label>
|
||||||
|
|||||||
@@ -4198,6 +4198,7 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
||||||
|
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect((screen.getByTestId("task-source-provider-input") as HTMLInputElement).value).toBe("github");
|
expect((screen.getByTestId("task-source-provider-input") as HTMLInputElement).value).toBe("github");
|
||||||
@@ -4207,6 +4208,45 @@ describe("TaskDetailModal", () => {
|
|||||||
expect((screen.getByTestId("task-source-url-input") as HTMLInputElement).value).toBe("https://github.com/runfusion/fusion/issues/2473");
|
expect((screen.getByTestId("task-source-url-input") as HTMLInputElement).value).toBe("https://github.com/runfusion/fusion/issues/2473");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders source issue block below Model Configuration in edit mode", async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({
|
||||||
|
id: "FN-001",
|
||||||
|
column: "todo",
|
||||||
|
sourceIssue: {
|
||||||
|
provider: "github",
|
||||||
|
repository: "runfusion/fusion",
|
||||||
|
externalIssueId: "I_kgDOExample",
|
||||||
|
issueNumber: 2473,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
addToast={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
||||||
|
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const modelLabel = screen.getByText("Model Configuration");
|
||||||
|
const sourceLabel = screen.getByText("Source Issue");
|
||||||
|
const workflowSection = screen.getByTestId("workflow-steps-section");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
modelLabel.compareDocumentPosition(sourceLabel) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||||
|
expect(
|
||||||
|
sourceLabel.compareDocumentPosition(workflowSection) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("sends sourceIssue payload when source metadata is edited", async () => {
|
it("sends sourceIssue payload when source metadata is edited", async () => {
|
||||||
const { updateTask } = await import("../../api");
|
const { updateTask } = await import("../../api");
|
||||||
const mockUpdate = vi.mocked(updateTask);
|
const mockUpdate = vi.mocked(updateTask);
|
||||||
@@ -4235,6 +4275,7 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
||||||
|
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
|
||||||
fireEvent.change(screen.getByTestId("task-source-provider-input"), { target: { value: "gitlab" } });
|
fireEvent.change(screen.getByTestId("task-source-provider-input"), { target: { value: "gitlab" } });
|
||||||
fireEvent.change(screen.getByTestId("task-source-repository-input"), { target: { value: "runfusion/dashboard" } });
|
fireEvent.change(screen.getByTestId("task-source-repository-input"), { target: { value: "runfusion/dashboard" } });
|
||||||
fireEvent.change(screen.getByTestId("task-source-external-id-input"), { target: { value: "I_kgDONew" } });
|
fireEvent.change(screen.getByTestId("task-source-external-id-input"), { target: { value: "I_kgDONew" } });
|
||||||
@@ -4283,6 +4324,7 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
||||||
|
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
|
||||||
fireEvent.change(screen.getByTestId("task-source-provider-input"), { target: { value: "" } });
|
fireEvent.change(screen.getByTestId("task-source-provider-input"), { target: { value: "" } });
|
||||||
fireEvent.change(screen.getByTestId("task-source-repository-input"), { target: { value: "" } });
|
fireEvent.change(screen.getByTestId("task-source-repository-input"), { target: { value: "" } });
|
||||||
fireEvent.change(screen.getByTestId("task-source-external-id-input"), { target: { value: "" } });
|
fireEvent.change(screen.getByTestId("task-source-external-id-input"), { target: { value: "" } });
|
||||||
@@ -4323,6 +4365,7 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
||||||
|
fireEvent.click(screen.getByTestId("task-form-more-options-toggle"));
|
||||||
fireEvent.change(screen.getByTestId("task-source-provider-input"), { target: { value: "gitlab" } });
|
fireEvent.change(screen.getByTestId("task-source-provider-input"), { target: { value: "gitlab" } });
|
||||||
fireEvent.click(screen.getByText("Save"));
|
fireEvent.click(screen.getByText("Save"));
|
||||||
|
|
||||||
|
|||||||
@@ -1382,6 +1382,30 @@ describe("TaskForm focus behavior (FN-1459)", () => {
|
|||||||
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders renderBelowModelConfiguration content below model section in More options", async () => {
|
||||||
|
renderTaskForm({
|
||||||
|
renderBelowModelConfiguration: <div data-testid="injected-below-model">Bottom slot content</div>,
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggle = screen.getByTestId("task-form-more-options-toggle");
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
const modelLabel = screen.getByText("Model Configuration");
|
||||||
|
const workflowLabel = screen.getByText("Workflow Steps");
|
||||||
|
const injectedBottom = screen.getByTestId("injected-below-model");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
modelLabel.compareDocumentPosition(injectedBottom) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||||
|
expect(
|
||||||
|
injectedBottom.compareDocumentPosition(workflowLabel) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBe(Node.DOCUMENT_POSITION_FOLLOWING);
|
||||||
|
});
|
||||||
|
|
||||||
it("hides dependencies section when hideDependencies is true", async () => {
|
it("hides dependencies section when hideDependencies is true", async () => {
|
||||||
renderTaskForm({ hideDependencies: true });
|
renderTaskForm({ hideDependencies: true });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user