Files
ttulttul-ComfyUI-Modal/tests/test_web_modal_toggle.py
T

504 lines
28 KiB
Python

"""Regression tests for the frontend Modal queue shim."""
from __future__ import annotations
from pathlib import Path
def _modal_toggle_source() -> str:
"""Return the current frontend extension source."""
return (Path(__file__).resolve().parents[1] / "web" / "modal_toggle.js").read_text(encoding="utf-8")
def test_synthetic_status_event_matches_comfyui_status_shape() -> None:
"""Synthetic status events should use the same detail shape as websocket status events."""
source = _modal_toggle_source()
assert 'dispatchSyntheticApiEvent("status", statusPayload(1));' in source
assert 'dispatchSyntheticApiEvent("status", statusPayload(0));' in source
def test_synthetic_execution_events_match_comfyui_execution_shapes() -> None:
"""Synthetic execution events should mirror ComfyUI's websocket adapter payloads."""
source = _modal_toggle_source()
assert 'dispatchSyntheticApiEvent("execution_start", {' in source
assert "timestamp: nowMs()," in source
assert 'dispatchSyntheticApiEvent("executing", displayNode);' in source
assert 'dispatchSyntheticApiEvent("notification", {' in source
assert "Waiting for a machine on Modal." in source
def test_global_modal_status_badge_is_installed() -> None:
"""The frontend should expose a dedicated global Modal activity indicator."""
source = _modal_toggle_source()
assert 'element.id = "comfy-modal-global-status";' in source
assert "Syncing graph with Modal" in source
assert "Waiting for Modal startup" in source
assert "Receiving Modal outputs" in source
assert "Modal workflow running on" in source
assert "setGlobalStatusBatchProgress(promptId, value, maxValue)" in source
assert "batchValue: state.batchValue ?? null," in source
assert "batchMax: state.batchMax ?? null," in source
assert "statusMessage: state.statusMessage ?? null," in source
assert "statusCurrent: state.statusCurrent ?? null," in source
assert "statusTotal: state.statusTotal ?? null," in source
assert "linear-gradient(90deg" in source
assert "installGlobalStatusStyles()" in source
assert "function pruneGlobalStatusStates()" in source
assert "function effectiveGlobalStatusPhase(promptId, phase)" in source
assert "function promptHasLiveRemoteWork(promptId)" in source
assert "function reconcilePromptGlobalStatus(promptId)" in source
assert "function promptRemoteNodeCount(promptId, fallbackCount = 1)" in source
assert "nodeCount: promptRemoteNodeCount(promptId, nodeCount)," in source
assert "hasRemoteExecutionStarted: false" in source
assert "Waiting for Modal container" in source
assert "Starting Modal component" in source
assert "Cancelling Modal workflow" in source
assert "function promptActiveNodeIsLive(promptId)" in source
def test_empty_modal_status_events_do_not_show_global_pill() -> None:
"""Queue requests without Modal-enabled nodes should not create global Modal UI state."""
source = _modal_toggle_source()
assert "if (nodeIds.length === 0 && components.length === 0) {\n return;\n }" in source
def test_remote_modal_status_tracks_active_node_ids() -> None:
"""The frontend should track and highlight the currently active remote node."""
source = _modal_toggle_source()
assert "activeNodeId: null" in source
assert "hasStreamedProgress: false" in source
assert "descendantNodeIdsByAncestor: new Map()" in source
assert "function setPromptActiveNode(promptId, activeNodeId)" in source
assert "detail.active_node_id" in source
assert "clearPromptRemoteStates(promptId)" in source
def test_interrupted_prompts_clear_modal_ui_by_prompt_id() -> None:
"""Interrupt cleanup should not depend on native execution events naming a specific node."""
source = _modal_toggle_source()
assert "const modalTerminalPromptStates = new Map();" in source
assert "const modalCancellingPromptIds = new Set();" in source
assert "function isPromptTerminal(promptId)" in source
assert "function isPromptCancelling(promptId)" in source
assert "function markPromptTerminal(promptId, phase)" in source
assert 'if (detail.phase === "execution_interrupted") {' in source
assert "function handlePromptInterruption(promptId)" in source
assert "modalCancellingPromptIds.delete(promptId);" in source
assert 'markPromptTerminal(promptId, "execution_interrupted");' in source
assert "clearGlobalStatusPhase(promptId);" in source
assert "clearPromptRemoteStates(promptId);" in source
assert 'api.addEventListener("execution_interrupted", (event) => {' in source
assert "const promptId = String(eventDetail(event).prompt_id ?? \"\");" in source
assert "handlePromptInterruption(promptId);" in source
def test_error_prompts_clear_remote_node_visual_state() -> None:
"""Prompt errors should not leave stale remote node colors behind for the next submission."""
source = _modal_toggle_source()
assert "function clearPromptRemoteNodeVisuals(promptId)" in source
assert "for (const [nodeIdValue, state] of modalNodeStates.entries()) {" in source
assert "clearNodeTimer(nodeIdValue);" in source
assert "clearNodeProgress(nodeIdValue, promptId);" in source
assert "clearNodeCached(nodeIdValue, promptId);" in source
assert "modalNodeStates.delete(nodeIdValue);" in source
assert "clearPromptRemoteNodeVisuals(promptId);" in source
assert "setNodesPhase(componentNodeIds, STATE_ERROR, promptId, detail.exception_message);" not in source
def test_remote_modal_uses_distinct_ready_active_and_complete_colors() -> None:
"""The frontend should distinguish ready, active, and completed remote nodes visually."""
source = _modal_toggle_source()
assert 'const READY_ACTIVE_COMPONENT_BORDER_COLOR = "#22c55e";' in source
assert 'const READY_INACTIVE_COMPONENT_BORDER_COLOR = "#166534";' in source
assert 'const STARTING_BORDER_COLOR = "#eab308";' in source
assert 'const ACTIVE_BORDER_COLOR = "#a855f7";' in source
assert 'const COMPLETE_BORDER_COLOR = "#004FA4";' in source
assert 'const COMPLETE_FILL_COLOR = "#001C71";' in source
assert 'const FINALIZING_NODE_BORDER_COLOR = "#00358A";' in source
assert 'const CANCELLING_BORDER_COLOR = "#fb7185";' in source
assert 'const STATE_STARTING = "starting";' in source
assert 'const STATE_WAITING = "waiting";' in source
assert 'const STATE_FINALIZING = "finalizing";' in source
assert 'const STATE_CANCELLING = "cancelling";' in source
assert 'const STATE_READY = "ready";' in source
assert 'const STATE_ACTIVE = "active";' in source
assert 'detail.phase === "execution_success"' in source
assert "phase === STATE_COMPLETE || phase === STATE_FINALIZING" in source
assert "state?.phase === STATE_STARTING" in source
assert "dot.style.background = STARTING_BORDER_COLOR;" in source
assert "state?.phase === STATE_FINALIZING" in source
assert "borderColor = FINALIZING_NODE_BORDER_COLOR;" in source
assert "setNodesPhase(nodeIds, STATE_FINALIZING, promptId);" in source
def test_cancel_click_shows_immediate_modal_cancelling_feedback() -> None:
"""Cancel should paint Modal prompts as cancelling before backend cleanup finishes."""
source = _modal_toggle_source()
assert "function patchInterruptFeedback()" in source
assert "api.fetchApi = function modalFetchApi(resource, options) {" in source
assert "function promptIdsFromInterruptRequest(resource, options)" in source
assert 'route.includes("/interrupt")' in source
assert "function markPromptCancellationRequested(promptId)" in source
assert "modalCancellingPromptIds.add(promptId);" in source
assert "setGlobalStatusPhase(promptId, STATE_CANCELLING" in source
assert "setNodesPhase(remoteNodeIds, STATE_CANCELLING, promptId);" in source
assert "if (isPromptCancelling(promptId)) {\n return;\n }" in source
assert "patchInterruptFeedback();" in source
def test_starting_modal_status_marks_component_before_remote_progress() -> None:
"""Dispatch-time Modal status should mark the component while remote progress is still pending."""
source = _modal_toggle_source()
assert "if (detail.phase === STATE_STARTING) {" in source
assert "setGlobalStatusPhase(promptId, STATE_STARTING, nodeIds.length, {" in source
assert "setNodesPhase(nodeIds, STATE_STARTING, promptId);" in source
assert "phases.find((state) => state.phase === STATE_STARTING)" in source
assert "[STATE_SETUP, STATE_STARTING, STATE_READY, STATE_ACTIVE, STATE_CANCELLING, STATE_ERROR]" in source
assert "activeState.phase === STATE_STARTING" in source
assert "STARTING_BORDER_COLOR" in source
def test_remote_modal_nodes_show_component_badges() -> None:
"""Remote nodes should render a compact component label badge."""
source = _modal_toggle_source()
assert "componentLabelByMember: new Map()," in source
assert "promptState.componentLabelByMember.set(componentNodeId, componentLabel);" in source
assert "componentLabel: promptState?.componentLabelByMember.get(nodeId(node)) ?? null," in source
assert "if (state?.componentLabel) {" in source
assert "ctx.arc(badgeX, badgeY, badgeRadius, 0, Math.PI * 2);" in source
assert "ctx.fillText(String(state.componentLabel), badgeX, badgeY + 0.5 / scale);" in source
def test_global_modal_status_badge_supports_setup_and_finalizing_details() -> None:
"""The frontend should surface detailed setup and result-receive messages in the global pill."""
source = _modal_toggle_source()
assert 'const FINALIZING_BORDER_COLOR = "#3b82f6";' in source
assert "detail.status_message ?? null" in source
assert "detail.status_current ?? null" in source
assert "detail.status_total ?? null" in source
assert "setGlobalStatusPhase(promptId, STATE_SETUP, nodeIds.length, {" in source
assert "if (detail.phase === STATE_FINALIZING) {" in source
assert "dot.style.background = FINALIZING_BORDER_COLOR;" in source
assert "READY_BORDER_COLOR" not in source
assert 'text.textContent = activeState.statusMessage ?? "Receiving Modal outputs";' in source
def test_queue_success_marks_all_remote_nodes_ready_before_component_execution() -> None:
"""Once the Modal route accepts the prompt, all remote nodes should flip from setup to ready."""
source = _modal_toggle_source()
assert "const modalQueuedPromptIds = new Set();" in source
assert "function markPromptQueuedBehindActiveModal(promptId)" in source
assert "function isPromptQueuedBehindActiveModal(promptId)" in source
assert "const queuedBehindActiveModal =" in source
assert "if (!queuedBehindActiveModal) {" in source
assert "const resolvedRemoteNodeIds = (responsePayload.modal_remote_node_ids ?? []).map((nodeIdValue) =>" in source
assert "const resolvedComponents = Array.isArray(responsePayload.modal_components)" in source
assert "registerPromptComponents(promptId, resolvedRemoteNodeIds, resolvedComponents);" in source
assert "endSyntheticExecutionUi(promptId);" in source
assert 'setGlobalStatusPhase(promptId, STATE_WAITING, acceptedRemoteNodeIds.length, {' in source
assert 'message: "Waiting for Modal startup",' in source
assert "setNodesPhase(acceptedRemoteNodeIds, STATE_READY, promptId);" in source
def test_completed_remote_nodes_clear_stale_global_status_entries() -> None:
"""The global pill should clear once a prompt has no active remote work left."""
source = _modal_toggle_source()
assert "promptState?.hasRemoteExecutionStarted &&\n nodeStates.some((state) => state.phase === STATE_READY)" in source
assert "nodeStates.some((state) => state.phase === STATE_READY || state.phase === STATE_COMPLETE)" not in source
assert "modalGlobalStatusStates.delete(promptId);" in source
assert "reconcilePromptGlobalStatus(promptId);" in source
assert "promptActiveNodeIsLive(promptId)" in source
assert "activeNodeState?.promptId === promptId && activeNodeState.phase === STATE_ACTIVE" in source
assert "if (promptState?.activeNodeId === String(currentNodeId))" in source
def test_streamed_modal_progress_takes_precedence_over_proxy_events() -> None:
"""Once streamed node progress starts, coarse proxy execution events should stop overriding it."""
source = _modal_toggle_source()
assert "promptState.hasStreamedProgress = true;" in source
assert "promptState.hasRemoteExecutionStarted = true;" in source
assert "if (promptState.hasStreamedProgress && phase === EXECUTION_PHASE)" in source
def test_streamed_modal_execution_ends_synthetic_setup_without_waiting_for_final_executed() -> None:
"""Real streamed execution should end synthetic setup on progress, not on the first executed node."""
source = _modal_toggle_source()
assert "if (detail.phase === EXECUTION_PHASE) {\n clearPromptQueued(promptId);\n endSyntheticExecutionUi(promptId);" in source
assert "function handleModalProgress(event)" in source
assert " endSyntheticExecutionUi(promptId);" in source
assert 'endSyntheticExecutionUi(String(eventDetail(event).prompt_id ?? ""));' not in source
def test_streamed_modal_node_progress_updates_active_overlay() -> None:
"""The frontend should listen for numeric Modal node progress and render it on the node."""
source = _modal_toggle_source()
assert 'const PROGRESS_FADE_MS = 900;' in source
assert "function fadeNodeProgress(nodeIdValue, promptId)" in source
assert "function progressVisualOpacity(progressState)" in source
assert 'api.addEventListener("modal_progress", handleModalProgress);' in source
assert "function handleModalProgress(event)" in source
assert "if (detail.aggregate_only) {" in source
assert "setNodeBatchProgress(" in source
assert "function setNodeProgress(nodeIdValue, promptId, value, maxValue)" in source
assert "function setNodeProgressLane(nodeIdValue, promptId, laneId, value, maxValue, itemIndex, setupOnly = false)" in source
assert "function clearNodeProgressLane(nodeIdValue, promptId, laneId)" in source
assert "function clearNodeProgress(nodeIdValue, promptId)" in source
assert "fadeNodeProgress(nodeIdValue, promptId);" in source
assert "state?.progress" in source
assert "state?.progressLanes" in source
assert "detail.lane_id != null" in source
assert "clearNodeProgressLane(progressNodeId, promptId, String(detail.lane_id));" in source
assert "setNodeProgressLane(" in source
assert "function deriveRemoteNodePhase(phase, hasLiveProgress)" in source
assert "isActiveRemoteNode: hasLiveProgress || promptState?.activeNodeId === nodeId(node)," in source
assert "isActiveComponentMember: isNodeInActiveComponent(state.promptId, nodeId(node))," in source
assert "isCachedRemoteNode: Boolean(cachedState)," in source
assert 'const panelY = node.size[1] + 6 / scale;' in source
assert 'ctx.roundRect(-borderWidth, panelY, barWidth, panelHeight, 10 / scale);' in source
assert 'const headerText = hasAggregateProgress' in source
def test_streamed_modal_node_progress_fades_previous_active_node() -> None:
"""Only same-component progress should complete the previous active node."""
source = _modal_toggle_source()
assert "function nodesShareRemoteComponent(promptId, leftNodeId, rightNodeId)" in source
assert "const previousActiveNodeId = promptState.activeNodeId;" in source
assert "previousActiveNodeId !== progressNodeId" in source
assert "nodesShareRemoteComponent(promptId, previousActiveNodeId, progressNodeId)" in source
assert "fadeNodeProgress(previousActiveNodeId, promptId);" in source
assert "setNodesPhase([previousActiveNodeId], STATE_COMPLETE, promptId);" in source
assert "setPromptActiveNode(promptId, progressNodeId);" in source
def test_parallel_component_progress_does_not_complete_other_active_nodes() -> None:
"""Parallel component progress should not treat the prompt-wide previous active node as complete."""
source = _modal_toggle_source()
assert "nodesShareRemoteComponent(promptId, previousActiveNodeId, nextActiveNodeId)" in source
assert "nodesShareRemoteComponent(promptId, previousActiveNodeId, progressNodeId)" in source
assert "promptState.activeNodeId && nodeIds.includes(promptState.activeNodeId)" in source
def test_streamed_modal_node_progress_completes_reported_ancestors() -> None:
"""When a remote node becomes active, reported upstream nodes should be marked complete."""
source = _modal_toggle_source()
assert "function completeRemoteAncestorsBeforeActiveNode(promptId, ancestorNodeIds, activeNodeId)" in source
assert "detail.completed_ancestor_node_ids" in source
assert "setNodesPhase(completedNodeIds, STATE_COMPLETE, promptId);" in source
assert "completeRemoteAncestorsBeforeActiveNode(\n promptId,\n detail.completed_ancestor_node_ids,\n progressNodeId,\n );" in source
assert "completeRemoteAncestorsBeforeActiveNode(\n promptId,\n detail.completed_ancestor_node_ids,\n nextActiveNodeId,\n );" in source
def test_ready_updates_do_not_downgrade_completed_remote_nodes() -> None:
"""Coarse execution status should not turn already completed remote nodes green again."""
source = _modal_toggle_source()
assert "const existingState = modalNodeStates.get(currentNodeId);" in source
assert "phase === STATE_READY &&\n existingState?.promptId === promptId" in source
assert "[STATE_COMPLETE, STATE_FINALIZING, STATE_ERROR].includes(existingState.phase)" in source
def test_mapped_parallel_modal_progress_renders_multiple_lane_bars() -> None:
"""Parallel mapped Modal runs should render one local progress lane per active worker."""
source = _modal_toggle_source()
assert "const modalNodeProgressLanes = new Map();" in source
assert "const modalNodeBatchProgress = new Map();" in source
assert "visibleLaneProgress.length > 0" in source
assert "const laneColors = [" in source
assert 'const badgeText = hasBatchBadge' in source
assert 'const badgeY = panelY + panelPaddingY;' in source
assert "if (detail.clear) {" in source
assert "clearNodeProgress(progressNodeId, promptId);" in source
assert 'let laneY = barY;' in source
assert "laneNodeIdsByLane: new Map()," in source
assert "representativeNodeIdByMember: new Map()," in source
assert "function deleteNodeProgressLane(nodeIdValue, promptId, laneId)" in source
assert "function laneOwnerKey(promptId, nodeIdValue, laneId)" in source
assert "promptState.laneNodeIdsByLane.set(safeLaneKey, safeNodeIdValue);" in source
assert "const setupProgressLanes = progressLanes.filter((laneProgress) => laneProgress.setupOnly);" in source
assert "const activeProgressLanes = progressLanes.filter((laneProgress) => !laneProgress.setupOnly);" in source
assert "laneProgress.setupOnly" in source
assert 'laneColor.replace("0.94)", `${0.28 + elapsedPulse * 0.22})`)' in source
def test_mapped_lane_setup_events_render_placeholder_lane_bars() -> None:
"""Lane setup events should render translucent placeholder bars before real node progress arrives."""
source = _modal_toggle_source()
assert "function setNodeProgressLane(nodeIdValue, promptId, laneId, value, maxValue, itemIndex, setupOnly = false)" in source
assert "setupOnly: Boolean(setupOnly)," in source
assert "Boolean(detail.setup_only)," in source
assert "const hasSetupLaneProgress = setupProgressLanes.length > 0;" in source
assert "const hasVisibleLaneProgress = visibleLaneProgress.length > 0;" in source
def test_ready_nodes_distinguish_active_component_members_and_cached_nodes() -> None:
"""Ready nodes should visually distinguish active-component membership and cache hits."""
source = _modal_toggle_source()
assert "function traceRoundedRectPath(ctx, x, y, width, height, radius)" in source
assert "function activeProgressNodeIds(promptId)" in source
assert "function isNodeInActiveComponent(promptId, nodeIdValue)" in source
assert "const promptActiveNodeId = promptState.activeNodeId;" in source
assert "for (const liveProgressNodeId of activeProgressNodeIds(promptId)) {" in source
assert "const liveProgressComponentNodeIds = resolveComponentNodeIds(promptId, liveProgressNodeId);" in source
assert "const descendantNodeIds = promptState.descendantNodeIdsByAncestor.get(safeNodeIdValue);" in source
assert "const pulseRate = state?.isCachedRemoteNode ? 2 : 6;" in source
assert "borderColor = state?.isActiveComponentMember" in source
assert "READY_INACTIVE_COMPONENT_BORDER_COLOR" in source
assert "READY_ACTIVE_COMPONENT_BORDER_COLOR" in source
assert "const cornerRadius = 12 / scale;" in source
assert "traceRoundedRectPath(" in source
assert "ctx.strokeRect(" not in source
def test_cached_node_hits_are_marked_without_fake_progress() -> None:
"""Cache-hit markers should be tracked separately from numeric progress."""
source = _modal_toggle_source()
assert "const modalNodeCachedStates = new Map();" in source
assert "function markNodeCached(nodeIdValue, promptId)" in source
assert "function clearNodeCached(nodeIdValue, promptId)" in source
assert "function nodeCachedState(nodeIdValue, promptId)" in source
assert "if (detail.cached_hit) {" in source
assert "const cachedNodeIds = new Set(" in source
assert "[detail.node_id, detail.display_node_id, detail.real_node_id]" in source
assert "for (const cachedNodeId of cachedNodeIds) {" in source
assert "markNodeCached(cachedNodeId, promptId);" in source
assert "const hasCachedPulse = Array.from(modalNodeCachedStates.values()).length > 0;" in source
def test_modal_context_menu_can_expand_required_upstream_nodes() -> None:
"""Right-clicking a node should offer a dry-run expansion action backed by the backend."""
source = _modal_toggle_source()
assert 'const MODAL_ANALYZE_ROUTE = MODAL_ROUTE.replace(/\\/queue_prompt$/, "/analyze_remote_nodes");' in source
assert "function workflowNodePath(node)" in source
assert "function findNodeByWorkflowPath(workflowPath)" in source
assert "function selectedWorkflowNodePaths(node)" in source
assert "function analyzeAndSetUpstreamRemoteNodes(node, value)" in source
assert "function installModalContextMenu(nodeType, nodeData)" in source
assert 'api.fetchApi(MODAL_ANALYZE_ROUTE, {' in source
assert "async beforeRegisterNodeDef(nodeType, nodeData)" in source
assert "installModalContextMenu(nodeType, nodeData);" in source
assert 'content: "Modal"' in source
assert "has_submenu: true" in source
assert "submenu: {" in source
assert '"Enable on Upstream Nodes"' in source
assert '"Enable on Upstream Nodes for Selection"' in source
assert '"Disable on Upstream Nodes"' in source
assert '"Disable on Upstream Nodes for Selection"' in source
assert '"Enable All Nodes"' in source
assert '"Disable All Nodes"' in source
assert '"Modal: Enable on Upstream Nodes"' not in source
assert '"Modal: Disable on Upstream Nodes"' not in source
assert '"Modal: Enable All Nodes"' not in source
assert "analyzeAndSetUpstreamRemoteNodes(this, true)" in source
assert "analyzeAndSetUpstreamRemoteNodes(this, false)" in source
assert "setAllEligibleWorkflowNodesRemote(true);" in source
assert "setAllEligibleWorkflowNodesRemote(false);" in source
def test_modal_context_menu_marks_nodes_across_subgraphs() -> None:
"""The UI action should be able to resolve and mark nested workflow-node paths."""
source = _modal_toggle_source()
assert "function rootGraph()" in source
assert "function findSomethingInAllSubgraphs(matcher)" in source
assert "function allWorkflowNodes()" in source
assert "function findContainingSubgraphNode(subgraphId)" in source
assert "candidate.subgraph?.id === subgraphId" in source
assert "function setWorkflowNodePathsRemote(workflowNodePaths, value)" in source
assert "setRemoteFlag(node, value);" in source
assert "node.__modalToggleWidget.value = enabled;" in source
def test_modal_context_menu_can_set_all_eligible_nodes() -> None:
"""The graph-wide menu actions should toggle every eligible live workflow node."""
source = _modal_toggle_source()
assert "function setAllEligibleWorkflowNodesRemote(value)" in source
assert "for (const node of allWorkflowNodes()) {" in source
assert "if (!isEligibleNode(node)) {" in source
assert "setRemoteFlag(node, value);" in source
assert 'const actionLabel = value ? "Enabled" : "Disabled";' in source
assert "${actionLabel} Modal on ${appliedCount} node" in source
def test_prompt_component_registration_does_not_shrink_remote_node_count() -> None:
"""Per-component status updates should not overwrite the prompt-wide remote node list."""
source = _modal_toggle_source()
assert "const mergedRemoteNodeIds = new Set(remoteNodeIds.map((nodeIdValue) => String(nodeIdValue)));" in source
assert "if (promptState.remoteNodeIds.length === 0) {" in source
assert "const mergedRemoteNodeIds = new Set(promptState.remoteNodeIds);" in source
def test_prompt_cleanup_prunes_orphaned_global_status_entries() -> None:
"""Workflow cleanup should remove stale global badge states once prompt state is gone."""
source = _modal_toggle_source()
assert "pruneGlobalStatusStates();" in source
assert "refreshGlobalStatusElement();" in source
def test_subgraph_descendant_states_percolate_to_visible_ancestor_nodes() -> None:
"""Subgraph-expanded remote prompt ids should aggregate their phase onto visible ancestor nodes."""
source = _modal_toggle_source()
assert "function ancestorNodeIds(nodeIdValue)" in source
assert "function rebuildPromptAncestorMap(promptState)" in source
assert "function refreshAncestorNodePhase(promptId, ancestorNodeId, errorMessage)" in source
assert "promptState.descendantNodeIdsByAncestor.get(ancestorNodeId)" in source
assert "descendantStates.every((state) => state.phase === STATE_COMPLETE)" in source
def test_modal_ui_refreshes_after_visibility_or_focus_returns() -> None:
"""Background-tab throttling should not leave the status pill stale after refocus."""
source = _modal_toggle_source()
assert 'const MODAL_PROGRESS_STATE_ROUTE = MODAL_ROUTE.replace(/\\/queue_prompt$/, "/progress_state");' in source
assert 'const COMFY_QUEUE_ROUTE = "/queue";' in source
assert 'const COMFY_HISTORY_ROUTE = "/history";' in source
assert "const REFOCUS_STALE_PROMPT_GRACE_MS = 30000;" in source
assert "function refreshModalUiAfterVisibilityChange()" in source
assert "function replayModalUiEventsAfterVisibilityChange()" in source
assert "function replayModalUiEvent(eventRecord)" in source
assert "function reconcileModalUiAfterVisibilityChange()" in source
assert "function activeModalUiPromptIds()" in source
assert "function promptIdsFromQueuePayload(queuePayload)" in source
assert "function historyPayloadHasPrompt(historyPayload, promptId)" in source
assert "function clearRefocusCompletedPrompt(promptId, phase)" in source
assert "modalVisibilityRefreshInFlight" in source
assert "api.fetchApi(" in source
assert "fetchComfyJson(COMFY_QUEUE_ROUTE)" in source
assert "queuedPromptIds.has(promptId)" in source
assert "`${COMFY_HISTORY_ROUTE}/${encodeURIComponent(promptId)}`" in source
assert 'clearRefocusCompletedPrompt(promptId, "execution_success");' in source
assert 'clearRefocusCompletedPrompt(promptId, "stale_refocus_cleanup");' in source
assert ".then(() => reconcileModalUiAfterVisibilityChange())" in source
assert "handleModalStatus({ detail: payload });" in source
assert "handleModalProgress({ detail: payload });" in source
assert 'document.addEventListener("visibilitychange"' in source
assert 'window.addEventListener("focus", refreshModalUiAfterVisibilityChange);' in source