All files / json-viewer/src/search scheduler.ts

92.85% Statements 13/14
100% Branches 10/10
87.5% Functions 7/8
90.9% Lines 10/11

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                        387x                                             170x 170x 2x   1x     1x     168x   168x 1x     167x    
/**
 * Yielding primitives for the chunked scan (SPEC.md ยง4.4).
 *
 * Searching 4.5 M nodes takes about a second of pure work. Doing it in one go
 * blocks input, animation and paint for that whole second; doing it in slices
 * costs a few percent more total work and keeps the tab alive. These two
 * helpers are what makes the difference, and they are module-level functions
 * precisely so the tests can stub them.
 */
 
/** Monotonic-ish clock, with a fallback for environments without `performance`. */
export function now(): number {
  return typeof performance !== "undefined" && typeof performance.now === "function"
    ? performance.now()
    : Date.now();
}
 
interface SchedulerLike {
  postTask?: (
    callback: () => void,
    options?: { priority?: string },
  ) => Promise<unknown>;
}
 
/**
 * Hand control back to the browser, then resume.
 *
 * The ladder is deliberate: `scheduler.postTask` at background priority is the
 * only one that lets the browser run input and rendering *first* and come back
 * when it is genuinely idle; `requestIdleCallback` is the older approximation;
 * `setTimeout(0)` is the floor that exists everywhere, including Node and
 * jsdom. Resolution is per call rather than cached at module load so a test
 * can stub any rung.
 */
export function scheduleYield(): Promise<void> {
  const scheduler = (globalThis as { scheduler?: SchedulerLike }).scheduler;
  if (scheduler && typeof scheduler.postTask === "function") {
    return scheduler
      .postTask(() => undefined, { priority: "background" })
      .then(() => undefined)
      // A postTask promise rejects if the task is aborted; falling through to
      // resolved keeps the scan alive rather than failing the whole search.
      .catch(() => undefined);
  }
 
  const idle = (globalThis as { requestIdleCallback?: (cb: () => void) => number })
    .requestIdleCallback;
  if (typeof idle === "function") {
    return new Promise((resolve) => idle(() => resolve()));
  }
 
  return new Promise((resolve) => setTimeout(resolve, 0));
}