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

171 lines
8.1 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 "Modal workflow running on" in source
assert "installGlobalStatusStyles()" in source
assert "function pruneGlobalStatusStates()" in source
assert "function effectiveGlobalStatusPhase(promptId, phase)" 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_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_BORDER_COLOR = "#22c55e";' in source
assert 'const ACTIVE_BORDER_COLOR = "#a855f7";' in source
assert 'const COMPLETE_BORDER_COLOR = "#16a34a";' in source
assert 'const STATE_WAITING = "waiting";' in source
assert 'const STATE_READY = "ready";' in source
assert 'const STATE_ACTIVE = "active";' in source
assert 'detail.phase === "execution_success"' 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 "if (promptState.hasStreamedProgress && phase !== STATE_ERROR)" 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 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 'api.addEventListener("modal_progress", handleModalProgress);' in source
assert "function handleModalProgress(event)" in source
assert "function setNodeProgress(nodeIdValue, promptId, value, maxValue)" in source
assert "function setNodeProgressLane(nodeIdValue, promptId, laneId, value, maxValue, itemIndex)" in source
assert "function clearNodeProgressLane(nodeIdValue, promptId, laneId)" in source
assert "function clearNodeProgress(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 'ctx.fillText(`${Math.round(progressRatio * 100)}%`' 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 "progressLanes.length > 0" in source
assert "const laneColors = [" in source
assert 'ctx.fillText(`${progressLanes.length}x`' 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 analyzeAndMarkRequiredRemoteNodes(node)" 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 '"Modal: Include Required Upstream Nodes"' in source
assert '"Modal: Include Required Upstream Nodes for Selection"' 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 findContainingSubgraphNode(subgraphId)" in source
assert "candidate.subgraph?.id === subgraphId" in source
assert "function markWorkflowNodePathsRemote(workflowNodePaths)" in source
assert "setRemoteFlag(node, true);" 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 "function refreshModalUiAfterVisibilityChange()" in source
assert 'document.addEventListener("visibilitychange"' in source
assert 'window.addEventListener("focus", refreshModalUiAfterVisibilityChange);' in source