feat(FN-3711): add inline fast mode toggle and wire shared state in peer ex
This merge introduces several dashboard and engine improvements: agent inbox wake controls (FN-3087, FN-3588) with immediate message triggers; separated plugin lifecycle from setup probe state (FN-3704); assigned-agent triage inheritance in the engine (FN-3703); inline fast mode toggle in the task d Fusion-Task-Id: FN-3711
This commit is contained in:
@@ -308,6 +308,7 @@ For related global/project configuration behavior, see [Settings reference](./se
|
|||||||
Inspect task definition, logs, comments, documents, workflow outcomes, model overrides, and task routing from a single modal.
|
Inspect task definition, logs, comments, documents, workflow outcomes, model overrides, and task routing from a single modal.
|
||||||
|
|
||||||
- The priority chip in task metadata is now an inline picker: you can change priority directly from the chip without entering full edit mode.
|
- The priority chip in task metadata is now an inline picker: you can change priority directly from the chip without entering full edit mode.
|
||||||
|
- Execution mode now also has a read-mode inline control: a lightning-bolt toggle lets you switch Fast mode on/off from task metadata without opening the full edit form.
|
||||||
|
|
||||||
### Logs → Agent Log view
|
### Logs → Agent Log view
|
||||||
|
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ Execution mode can be set during task creation or editing:
|
|||||||
|
|
||||||
- **Via API**: Include `executionMode` field in task create/update payload
|
- **Via API**: Include `executionMode` field in task create/update payload
|
||||||
- **Via dashboard**: Select execution mode in the task creation dialog or task detail modal
|
- **Via dashboard**: Select execution mode in the task creation dialog or task detail modal
|
||||||
|
- **Task detail quick toggle**: In read mode, use the inline lightning-bolt control in task metadata to switch between **Standard** and **Fast** without entering full edit mode
|
||||||
- **Values**: `"standard"` (default) or `"fast"`
|
- **Values**: `"standard"` (default) or `"fast"`
|
||||||
|
|
||||||
Example API payload:
|
Example API payload:
|
||||||
@@ -215,6 +216,8 @@ Research document content appears in the existing **Documents** tab in Task Deta
|
|||||||
|
|
||||||
The task detail modal exposes multiple tabs.
|
The task detail modal exposes multiple tabs.
|
||||||
|
|
||||||
|
In read mode, task metadata includes lightweight inline controls: priority can be changed from the priority chip, and execution mode can be toggled with a one-click lightning-bolt fast-mode button (Fast ↔ Standard) without opening full edit mode.
|
||||||
|
|
||||||
Task settings edited from the modal now auto-save as you edit (change/blur with debounce for text-like fields). This includes title, description, dependencies, working/base branch (`branch`/`baseBranch`), workflow-step selection, model overrides in the edit form, and source issue metadata. The footer Save button remains available, but normal field edits no longer depend on a manual save click.
|
Task settings edited from the modal now auto-save as you edit (change/blur with debounce for text-like fields). This includes title, description, dependencies, working/base branch (`branch`/`baseBranch`), workflow-step selection, model overrides in the edit form, and source issue metadata. The footer Save button remains available, but normal field edits no longer depend on a manual save click.
|
||||||
|
|
||||||
The edit footer shows inline autosave state (saving/saved/error), and successful saves propagate the returned task through `onTaskUpdated` so open detail/list state stays fresh.
|
The edit footer shows inline autosave state (saving/saved/error), and successful saves propagate the returned task through `onTaskUpdated` so open detail/list state stays fresh.
|
||||||
|
|||||||
@@ -202,6 +202,31 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-execution-mode-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-left: var(--space-xs);
|
||||||
|
min-height: calc(var(--space-2xl) + var(--space-xs));
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-execution-mode-toggle svg {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-execution-mode-toggle--fast {
|
||||||
|
color: var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-execution-mode-toggle--saving {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-execution-mode-toggle:disabled {
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Error alert in task detail modal */
|
/* Error alert in task detail modal */
|
||||||
.detail-error-alert {
|
.detail-error-alert {
|
||||||
@@ -1442,6 +1467,11 @@
|
|||||||
min-height: var(--detail-priority-control-min-height);
|
min-height: var(--detail-priority-control-min-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-execution-mode-toggle {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
.detail-source-header {
|
.detail-source-header {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "./TaskDetailModal.css";
|
import "./TaskDetailModal.css";
|
||||||
import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { Pencil, Bot, X, ChevronDown, ChevronRight, GitBranch, ArrowLeft } from "lucide-react";
|
import { Pencil, Bot, X, ChevronDown, ChevronRight, GitBranch, ArrowLeft, Zap } from "lucide-react";
|
||||||
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
||||||
import { useMobileScrollLock } from "../hooks/useMobileScrollLock";
|
import { useMobileScrollLock } from "../hooks/useMobileScrollLock";
|
||||||
import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
||||||
@@ -538,6 +538,8 @@ export function TaskDetailContent({
|
|||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [inlinePriority, setInlinePriority] = useState<TaskPriority>(normalizeTaskPriorityValue(task.priority));
|
const [inlinePriority, setInlinePriority] = useState<TaskPriority>(normalizeTaskPriorityValue(task.priority));
|
||||||
const [isSavingInlinePriority, setIsSavingInlinePriority] = useState(false);
|
const [isSavingInlinePriority, setIsSavingInlinePriority] = useState(false);
|
||||||
|
const [inlineExecutionMode, setInlineExecutionMode] = useState<"standard" | "fast">(normalizeExecutionModeValue(task.executionMode));
|
||||||
|
const [isSavingInlineExecutionMode, setIsSavingInlineExecutionMode] = useState(false);
|
||||||
const mountedRef = useRef(false);
|
const mountedRef = useRef(false);
|
||||||
|
|
||||||
// Split-menu dropdown state for footer actions
|
// Split-menu dropdown state for footer actions
|
||||||
@@ -600,6 +602,10 @@ export function TaskDetailContent({
|
|||||||
setInlinePriority(normalizeTaskPriorityValue(task.priority));
|
setInlinePriority(normalizeTaskPriorityValue(task.priority));
|
||||||
}, [task.id, task.priority]);
|
}, [task.id, task.priority]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInlineExecutionMode(normalizeExecutionModeValue(task.executionMode));
|
||||||
|
}, [task.id, task.executionMode]);
|
||||||
|
|
||||||
// Load merged settings for effective model resolution
|
// Load merged settings for effective model resolution
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -1019,6 +1025,30 @@ export function TaskDetailContent({
|
|||||||
}
|
}
|
||||||
}, [task.id, task.priority, projectId, inlinePriority, onTaskUpdated, addToast]);
|
}, [task.id, task.priority, projectId, inlinePriority, onTaskUpdated, addToast]);
|
||||||
|
|
||||||
|
const handleInlineExecutionModeToggle = useCallback(async () => {
|
||||||
|
const currentMode = normalizeExecutionModeValue(task.executionMode);
|
||||||
|
const nextMode = currentMode === "fast" ? "standard" : "fast";
|
||||||
|
const previousMode = inlineExecutionMode;
|
||||||
|
|
||||||
|
setInlineExecutionMode(nextMode);
|
||||||
|
setIsSavingInlineExecutionMode(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedTask = await updateTask(task.id, { executionMode: nextMode === "fast" ? "fast" : null }, projectId);
|
||||||
|
const normalizedUpdatedMode = normalizeExecutionModeValue(updatedTask.executionMode);
|
||||||
|
setInlineExecutionMode(normalizedUpdatedMode);
|
||||||
|
onTaskUpdated?.(updatedTask);
|
||||||
|
addToast(`Execution mode updated to ${normalizedUpdatedMode}`, "success");
|
||||||
|
} catch (err) {
|
||||||
|
setInlineExecutionMode(previousMode);
|
||||||
|
addToast(`Failed to update ${task.id}: ${getErrorMessage(err)}`, "error");
|
||||||
|
} finally {
|
||||||
|
if (mountedRef.current) {
|
||||||
|
setIsSavingInlineExecutionMode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [task.id, task.executionMode, projectId, inlineExecutionMode, onTaskUpdated, addToast]);
|
||||||
|
|
||||||
// Handle keyboard shortcuts for edit mode
|
// Handle keyboard shortcuts for edit mode
|
||||||
const handleEditKeyDown = useCallback((e: KeyboardEvent) => {
|
const handleEditKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
if (!isEditing) return;
|
if (!isEditing) return;
|
||||||
@@ -1851,6 +1881,19 @@ export function TaskDetailContent({
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm detail-execution-mode-toggle ${inlineExecutionMode === "fast" ? "detail-execution-mode-toggle--fast" : ""} ${isSavingInlineExecutionMode ? "detail-execution-mode-toggle--saving" : ""}`}
|
||||||
|
onClick={() => {
|
||||||
|
void handleInlineExecutionModeToggle();
|
||||||
|
}}
|
||||||
|
disabled={isSavingInlineExecutionMode}
|
||||||
|
aria-label={`Execution mode: ${inlineExecutionMode}`}
|
||||||
|
aria-pressed={inlineExecutionMode === "fast"}
|
||||||
|
>
|
||||||
|
<Zap aria-hidden="true" />
|
||||||
|
<span>{inlineExecutionMode === "fast" ? "Fast" : "Standard"}</span>
|
||||||
|
</button>
|
||||||
{provenanceDisplay && (
|
{provenanceDisplay && (
|
||||||
<div className="detail-provenance">
|
<div className="detail-provenance">
|
||||||
<GitBranch aria-hidden="true" />
|
<GitBranch aria-hidden="true" />
|
||||||
|
|||||||
@@ -941,6 +941,124 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(addToast).toHaveBeenCalledWith("Failed to update FN-001: Request failed", "error");
|
expect(addToast).toHaveBeenCalledWith("Failed to update FN-001: Request failed", "error");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("toggles inline execution mode from standard to fast", async () => {
|
||||||
|
const { updateTask } = await import("../../api");
|
||||||
|
const mockUpdate = vi.mocked(updateTask);
|
||||||
|
const addToast = vi.fn();
|
||||||
|
const onTaskUpdated = vi.fn();
|
||||||
|
const updatedTask = makeTask({ id: "FN-001", column: "todo", executionMode: "fast" });
|
||||||
|
mockUpdate.mockResolvedValueOnce(updatedTask as Task);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({ id: "FN-001", column: "todo", executionMode: "standard" })}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
onTaskUpdated={onTaskUpdated}
|
||||||
|
addToast={addToast}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggle = screen.getByRole("button", { name: "Execution mode: standard" });
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith("FN-001", { executionMode: "fast" }, undefined);
|
||||||
|
});
|
||||||
|
expect(onTaskUpdated).toHaveBeenCalledWith(updatedTask);
|
||||||
|
expect(addToast).toHaveBeenCalledWith("Execution mode updated to fast", "success");
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "Execution mode: fast" })).toHaveAttribute("aria-pressed", "true");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles inline execution mode from fast to standard", async () => {
|
||||||
|
const { updateTask } = await import("../../api");
|
||||||
|
const mockUpdate = vi.mocked(updateTask);
|
||||||
|
mockUpdate.mockResolvedValueOnce(makeTask({ id: "FN-001", column: "todo", executionMode: null }) as Task);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({ id: "FN-001", column: "todo", executionMode: "fast" })}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
addToast={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Execution mode: fast" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith("FN-001", { executionMode: null }, undefined);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "Execution mode: standard" })).toHaveAttribute("aria-pressed", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reverts inline execution mode when save fails", async () => {
|
||||||
|
const { updateTask } = await import("../../api");
|
||||||
|
const mockUpdate = vi.mocked(updateTask);
|
||||||
|
const addToast = vi.fn();
|
||||||
|
mockUpdate.mockRejectedValueOnce(new Error("Request failed"));
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({ id: "FN-001", column: "todo", executionMode: "standard" })}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
addToast={addToast}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Execution mode: standard" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith("FN-001", { executionMode: "fast" }, undefined);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "Execution mode: standard" })).toHaveAttribute("aria-pressed", "false");
|
||||||
|
});
|
||||||
|
expect(addToast).toHaveBeenCalledWith("Failed to update FN-001: Request failed", "error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables inline execution mode toggle while save is in-flight", async () => {
|
||||||
|
const { updateTask } = await import("../../api");
|
||||||
|
const mockUpdate = vi.mocked(updateTask);
|
||||||
|
mockUpdate.mockImplementationOnce(
|
||||||
|
() => new Promise((resolve) => setTimeout(() => resolve(makeTask({ executionMode: "fast" }) as Task), 100)),
|
||||||
|
);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({ id: "FN-001", column: "todo", executionMode: "standard" })}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
addToast={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggle = screen.getByRole("button", { name: "Execution mode: standard" });
|
||||||
|
fireEvent.click(toggle);
|
||||||
|
expect(toggle).toBeDisabled();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdate).toHaveBeenCalledWith("FN-001", { executionMode: "fast" }, undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("pre-populates form with existing task values", () => {
|
it("pre-populates form with existing task values", () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<TaskDetailModal
|
<TaskDetailModal
|
||||||
@@ -1655,7 +1773,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(screen.getByText("Execution Timing")).toBeInTheDocument();
|
expect(screen.getByText("Execution Timing")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Execution Details")).toBeInTheDocument();
|
expect(screen.getByText("Execution Details")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Loading token statistics…")).toBeDefined();
|
expect(screen.getByText("Loading token statistics…")).toBeDefined();
|
||||||
expect(screen.getByText("Fast")).toBeInTheDocument();
|
expect(screen.getAllByText("Fast").length).toBeGreaterThan(0);
|
||||||
expect(screen.getByText("executing")).toBeInTheDocument();
|
expect(screen.getByText("executing")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1743,7 +1861,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(screen.getByText("Workflow runtime")).toBeInTheDocument();
|
expect(screen.getByText("Workflow runtime")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Execution mode")).toBeInTheDocument();
|
expect(screen.getByText("Execution mode")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Runtime status")).toBeInTheDocument();
|
expect(screen.getByText("Runtime status")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Fast")).toBeInTheDocument();
|
expect(screen.getAllByText("Fast").length).toBeGreaterThan(0);
|
||||||
expect(screen.getByText("executing")).toBeInTheDocument();
|
expect(screen.getByText("executing")).toBeInTheDocument();
|
||||||
expect(screen.getByText((1200).toLocaleString())).toBeInTheDocument();
|
expect(screen.getByText((1200).toLocaleString())).toBeInTheDocument();
|
||||||
expect(screen.getByText((450).toLocaleString())).toBeInTheDocument();
|
expect(screen.getByText((450).toLocaleString())).toBeInTheDocument();
|
||||||
|
|||||||
@@ -306,6 +306,44 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(screen.getByText("Comments")).toBeTruthy();
|
expect(screen.getByText("Comments")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("inline execution mode toggle", () => {
|
||||||
|
it("renders standard mode as an unpressed toggle", () => {
|
||||||
|
render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({ column: "triage", executionMode: "standard" })}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
addToast={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggle = screen.getByRole("button", { name: "Execution mode: standard" });
|
||||||
|
expect(toggle).toHaveAttribute("aria-pressed", "false");
|
||||||
|
expect(toggle).toHaveTextContent("Standard");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders fast mode as a pressed toggle", () => {
|
||||||
|
render(
|
||||||
|
<TaskDetailModal
|
||||||
|
task={makeTask({ column: "todo", executionMode: "fast" })}
|
||||||
|
onClose={noop}
|
||||||
|
onMoveTask={noopMove}
|
||||||
|
onDeleteTask={noopDelete}
|
||||||
|
onMergeTask={noopMerge}
|
||||||
|
onOpenDetail={noopOpenDetail}
|
||||||
|
addToast={noop}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggle = screen.getByRole("button", { name: "Execution mode: fast" });
|
||||||
|
expect(toggle).toHaveAttribute("aria-pressed", "true");
|
||||||
|
expect(toggle).toHaveTextContent("Fast");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("appends daemon token query to attachment href/src URLs for direct browser loads", () => {
|
it("appends daemon token query to attachment href/src URLs for direct browser loads", () => {
|
||||||
localStorage.setItem("fn.authToken", "daemon-token");
|
localStorage.setItem("fn.authToken", "daemon-token");
|
||||||
|
|
||||||
@@ -1207,7 +1245,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(screen.getByText("Execution Timing")).toBeInTheDocument();
|
expect(screen.getByText("Execution Timing")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Execution Details")).toBeInTheDocument();
|
expect(screen.getByText("Execution Details")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Loading token statistics…")).toBeDefined();
|
expect(screen.getByText("Loading token statistics…")).toBeDefined();
|
||||||
expect(screen.getByText("Fast")).toBeInTheDocument();
|
expect(screen.getAllByText("Fast").length).toBeGreaterThan(0);
|
||||||
expect(screen.getByText("executing")).toBeInTheDocument();
|
expect(screen.getByText("executing")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1295,7 +1333,7 @@ describe("TaskDetailModal", () => {
|
|||||||
expect(screen.getByText("Workflow runtime")).toBeInTheDocument();
|
expect(screen.getByText("Workflow runtime")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Execution mode")).toBeInTheDocument();
|
expect(screen.getByText("Execution mode")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Runtime status")).toBeInTheDocument();
|
expect(screen.getByText("Runtime status")).toBeInTheDocument();
|
||||||
expect(screen.getByText("Fast")).toBeInTheDocument();
|
expect(screen.getAllByText("Fast").length).toBeGreaterThan(0);
|
||||||
expect(screen.getByText("executing")).toBeInTheDocument();
|
expect(screen.getByText("executing")).toBeInTheDocument();
|
||||||
expect(screen.getByText((1200).toLocaleString())).toBeInTheDocument();
|
expect(screen.getByText((1200).toLocaleString())).toBeInTheDocument();
|
||||||
expect(screen.getByText((450).toLocaleString())).toBeInTheDocument();
|
expect(screen.getByText((450).toLocaleString())).toBeInTheDocument();
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ vi.mock("lucide-react", () => ({
|
|||||||
ChevronDown: () => null,
|
ChevronDown: () => null,
|
||||||
ChevronRight: (props: any) => React.createElement("svg", { "data-testid": "chevron-right-icon", ...props }),
|
ChevronRight: (props: any) => React.createElement("svg", { "data-testid": "chevron-right-icon", ...props }),
|
||||||
ArrowLeft: () => null,
|
ArrowLeft: () => null,
|
||||||
|
Zap: () => null,
|
||||||
X: () => null,
|
X: () => null,
|
||||||
Maximize2: () => null,
|
Maximize2: () => null,
|
||||||
Minimize2: () => null,
|
Minimize2: () => null,
|
||||||
|
|||||||
Reference in New Issue
Block a user