All files / client/src/hooks use-simulation-lifecycle.ts

100% Statements 33/33
86.36% Branches 19/22
66.66% Functions 6/9
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                                                            289x     289x     289x   289x 2x     289x 2x 2x   2x 2x   2x 2x         289x 213x 213x 95x 95x     118x   6x   6x 1x   1x 1x       6x       289x 269x 7x 1x   1x 1x         289x                
import { useEffect, useRef, useCallback } from "react";
import type { IncomingArduinoMessage } from "@/types/websocket";
 
interface UseSimulationLifecycleOptions {
  code: string;
  simulationStatus: "idle" | "running" | "compiling" | "queued" | "paused";
  setSimulationStatus: (s: "idle" | "running" | "compiling" | "queued" | "paused") => void;
  sendMessage: (msg: IncomingArduinoMessage) => void;
  resetPinUI: (opts?: { keepDetected?: boolean }) => void;
  clearOutputs?: () => void;
  handlePause?: () => void;
  handleResume?: () => void;
  handleReset?: () => void;
  hasCompilationErrors?: boolean;
}
 
export function useSimulationLifecycle({
  code,
  simulationStatus,
  setSimulationStatus,
  sendMessage,
  resetPinUI,
  clearOutputs,
  handlePause,
  handleResume,
  handleReset,
  hasCompilationErrors = false,
}: UseSimulationLifecycleOptions) {
  // Trace state transitions for tests/debugging
   
  useEffect(() => { /* status changes handled by lifecycle */ }, [simulationStatus]);
 
  // Temporary suppression flag (used when inserting editor suggestions)
  const skipAutoStopRef = useRef(false);
 
  // Remember last code to detect *edits* (not initial mount)
  const prevCodeRef = useRef<string | null>(null);
 
  const suppressAutoStopOnce = useCallback(() => {
    skipAutoStopRef.current = true;
  }, []);
 
  const stopSimulation = useCallback(() => {
    try {
      sendMessage({ type: "stop_simulation" });
    } catch {}
    try {
      setSimulationStatus("idle");
    } catch {}
    try {
      resetPinUI();
    } catch {}
  }, [sendMessage, setSimulationStatus, resetPinUI]);
 
  // Watch for code edits and stop running/paused simulation (unless suppressed)
  useEffect(() => {
    const prev = prevCodeRef.current;
    if (prev === null) {
      prevCodeRef.current = code;
      return;
    }
 
    if (prev === code) return;
 
    prevCodeRef.current = code;
 
    if ((simulationStatus === "running" || simulationStatus === "paused") && !skipAutoStopRef.current) {
      stopSimulation();
      // preserve detected pin modes when stopping due to edit
      try {
        resetPinUI({ keepDetected: true });
      } catch {}
    }
 
    skipAutoStopRef.current = false;
  }, [code, simulationStatus, stopSimulation, resetPinUI]);
 
  // Stop simulation automatically when compiler reports errors
  useEffect(() => {
    if (!hasCompilationErrors) return;
    if (simulationStatus === "running" || simulationStatus === "paused") {
      stopSimulation();
      // keep UI state consistent when compiler fails
      try {
        clearOutputs?.();
      } catch {}
    }
  }, [hasCompilationErrors, simulationStatus, stopSimulation, clearOutputs]);
 
  return {
    suppressAutoStopOnce,
    stopSimulation,
    pauseSimulation: handlePause ?? (() => {}),
    resumeSimulation: handleResume ?? (() => {}),
    resetSimulation: handleReset ?? (() => {}),
  } as const;
}