Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openclaw/src/channels/   (KI Agentensystem Version 22©)  Datei vom 26.3.2026 mit Größe 15 kB image not shown  

Quelle  status-reactions.test.ts

  Sprache: JAVA
 

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
  resolveToolEmoji,
  createStatusReactionController,
  DEFAULT_EMOJIS,
  DEFAULT_TIMING,
  CODING_TOOL_TOKENS,
  WEB_TOOL_TOKENS,
  type StatusReactionAdapter,
} from "./status-reactions.js";

// ─────────────────────────────────────────────────────────────────────────────
// Mock Adapter
// ─────────────────────────────────────────────────────────────────────────────

const createMockAdapter = () => {
  const calls: { method: string; emoji: string }[] = [];
  return {
    adapter: {
      setReaction: vi.fn(async (emoji: string) => {
        calls.push({ method: "set", emoji });
      }),
      removeReaction: vi.fn(async (emoji: string) => {
        calls.push({ method: "remove", emoji });
      }),
    } as StatusReactionAdapter,
    calls,
  };
};

const createEnabledController = (
  overrides: Partial<Parameters<typeof createStatusReactionController>[0]> = {},
) => {
  const { adapter, calls } = createMockAdapter();
  const controller = createStatusReactionController({
    enabled: true,
    adapter,
    initialEmoji: "��",
    ...overrides,
  });
  return { adapter, calls, controller };
};

const createSetOnlyController = () => {
  const calls: { method: string; emoji: string }[] = [];
  const adapter: StatusReactionAdapter = {
    setReaction: vi.fn(async (emoji: string) => {
      calls.push({ method: "set", emoji });
    }),
  };
  const controller = createStatusReactionController({
    enabled: true,
    adapter,
    initialEmoji: "��",
  });
  return { calls, controller };
};

function expectSetEmojiCall(calls: Array<{ method: string; emoji: string }>, emoji: string) {
  expect(calls).toContainEqual({ method: "set", emoji });
}

function expectArrayContainsAll(values: readonly string[], expected: readonly string[]) {
  expected.forEach((value) => {
    expect(values).toContain(value);
  });
}

function expectObjectHasKeys(value: Record<string, unknown>, keys: readonly string[]) {
  keys.forEach((key) => {
    expect(value).toHaveProperty(key);
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Tests
// ─────────────────────────────────────────────────────────────────────────────

describe("resolveToolEmoji", () => {
  it.each([
    { name: "returns coding emoji for exec tool", tool: "exec", expected: DEFAULT_EMOJIS.coding },
    {
      name: "returns coding emoji for process tool",
      tool: "process",
      expected: DEFAULT_EMOJIS.coding,
    },
    {
      name: "returns web emoji for web_search tool",
      tool: "web_search",
      expected: DEFAULT_EMOJIS.web,
    },
    { name: "returns web emoji for browser tool", tool: "browser", expected: DEFAULT_EMOJIS.web },
    {
      name: "returns tool emoji for unknown tool",
      tool: "unknown_tool",
      expected: DEFAULT_EMOJIS.tool,
    },
    { name: "returns tool emoji for empty string", tool: "", expected: DEFAULT_EMOJIS.tool },
    { name: "returns tool emoji for undefined", tool: undefined, expected: DEFAULT_EMOJIS.tool },
    { name: "is case-insensitive", tool: "EXEC", expected: DEFAULT_EMOJIS.coding },
    {
      name: "matches tokens within tool names",
      tool: "my_exec_wrapper",
      expected: DEFAULT_EMOJIS.coding,
    },
  ] satisfies Array<{ name: string; tool: string | undefined; expected: string }>)(
    "should $name",
    ({ tool, expected }) => {
      expect(resolveToolEmoji(tool, DEFAULT_EMOJIS)).toBe(expected);
    },
  );
});

describe("createStatusReactionController", () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.restoreAllMocks();
    vi.useRealTimers();
  });

  it("should not call adapter when disabled", async () => {
    const { adapter, calls } = createMockAdapter();
    const controller = createStatusReactionController({
      enabled: false,
      adapter,
      initialEmoji: "��",
    });

    void controller.setQueued();
    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(1000);

    expect(calls).toHaveLength(0);
  });

  it("should call setReaction with initialEmoji for setQueued immediately", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setQueued();
    await vi.runAllTimersAsync();

    expectSetEmojiCall(calls, "��");
  });

  it("should debounce setThinking and eventually call adapter", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setThinking();

    // Before debounce period
    await vi.advanceTimersByTimeAsync(500);
    expect(calls).toHaveLength(0);

    // After debounce period
    await vi.advanceTimersByTimeAsync(300);
    expectSetEmojiCall(calls, DEFAULT_EMOJIS.thinking);
  });

  it("should debounce setCompacting and eventually call adapter", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setCompacting();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    expectSetEmojiCall(calls, DEFAULT_EMOJIS.compacting);
  });

  it("should classify tool name and debounce", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setTool("exec");
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    expectSetEmojiCall(calls, DEFAULT_EMOJIS.coding);
  });

  const immediateTerminalCases = [
    {
      name: "setDone",
      run: (controller: ReturnType<typeof createStatusReactionController>) => controller.setDone(),
      expected: DEFAULT_EMOJIS.done,
    },
    {
      name: "setError",
      run: (controller: ReturnType<typeof createStatusReactionController>) => controller.setError(),
      expected: DEFAULT_EMOJIS.error,
    },
  ] as const;

  it.each(immediateTerminalCases)(
    "should execute $name immediately without debounce",
    async ({ run, expected }) => {
      const { calls, controller } = createEnabledController();

      await run(controller);
      await vi.runAllTimersAsync();

      expectSetEmojiCall(calls, expected);
    },
  );

  const terminalIgnoreCases = [
    {
      name: "ignore setThinking after setDone (terminal state)",
      terminal: (controller: ReturnType<typeof createStatusReactionController>) =>
        controller.setDone(),
      followup: (controller: ReturnType<typeof createStatusReactionController>) => {
        void controller.setThinking();
      },
    },
    {
      name: "ignore setTool after setError (terminal state)",
      terminal: (controller: ReturnType<typeof createStatusReactionController>) =>
        controller.setError(),
      followup: (controller: ReturnType<typeof createStatusReactionController>) => {
        void controller.setTool("exec");
      },
    },
  ] as const;

  it.each(terminalIgnoreCases)("should $name", async ({ terminal, followup }) => {
    const { calls, controller } = createEnabledController();

    await terminal(controller);
    const callsAfterTerminal = calls.length;
    followup(controller);
    await vi.advanceTimersByTimeAsync(1000);

    expect(calls.length).toBe(callsAfterTerminal);
  });

  it("should only fire last state when rapidly changing (debounce)", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(100);

    void controller.setTool("web_search");
    await vi.advanceTimersByTimeAsync(100);

    void controller.setTool("exec");
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    // Should only have the last one (exec → coding)
    const setEmojis = calls.filter((c) => c.method === "set").map((c) => c.emoji);
    expect(setEmojis).toEqual([DEFAULT_EMOJIS.coding]);
  });

  it("should deduplicate same emoji calls", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    const callsAfterFirst = calls.length;

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    // Should not add another call
    expect(calls.length).toBe(callsAfterFirst);
  });

  it("should cancel a pending compacting emoji before resuming thinking", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setCompacting();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs - 1);
    controller.cancelPending();
    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    const setEmojis = calls.filter((call) => call.method === "set").map((call) => call.emoji);
    expect(setEmojis).toEqual([DEFAULT_EMOJIS.thinking]);
  });

  it("should call removeReaction when adapter supports it and emoji changes", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setQueued();
    await vi.runAllTimersAsync();

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    // Should set thinking, then remove queued
    expectSetEmojiCall(calls, DEFAULT_EMOJIS.thinking);
    expect(calls).toContainEqual({ method: "remove", emoji: "��" });
  });

  it("should only call setReaction when adapter lacks removeReaction", async () => {
    const { calls, controller } = createSetOnlyController();

    void controller.setQueued();
    await vi.runAllTimersAsync();

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    // Should only have set calls, no remove
    const removeCalls = calls.filter((c) => c.method === "remove");
    expect(removeCalls).toHaveLength(0);
    expect(calls.filter((c) => c.method === "set").length).toBeGreaterThan(0);
  });

  it("should clear all known emojis when adapter supports removeReaction", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setQueued();
    await vi.runAllTimersAsync();

    await controller.clear();

    // Should have removed multiple emojis
    const removeCalls = calls.filter((c) => c.method === "remove");
    expect(removeCalls.length).toBeGreaterThan(0);
  });

  it("should handle clear gracefully when adapter lacks removeReaction", async () => {
    const { calls, controller } = createSetOnlyController();

    await controller.clear();

    // Should not throw, no remove calls
    const removeCalls = calls.filter((c) => c.method === "remove");
    expect(removeCalls).toHaveLength(0);
  });

  it("should restore initial emoji", async () => {
    const { calls, controller } = createEnabledController();

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    await controller.restoreInitial();

    expectSetEmojiCall(calls, "��");
  });

  it("should use custom emojis when provided", async () => {
    const { calls, controller } = createEnabledController({
      emojis: {
        thinking: "��",
        done: "��",
      },
    });

    void controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);

    expectSetEmojiCall(calls, "��");

    await controller.setDone();
    await vi.runAllTimersAsync();
    expectSetEmojiCall(calls, "��");
  });

  it("should use custom timing when provided", async () => {
    const { calls, controller } = createEnabledController({
      timing: {
        debounceMs: 100,
      },
    });

    void controller.setThinking();

    // Should not fire at 50ms
    await vi.advanceTimersByTimeAsync(50);
    expect(calls).toHaveLength(0);

    // Should fire at 100ms
    await vi.advanceTimersByTimeAsync(60);
    expectSetEmojiCall(calls, DEFAULT_EMOJIS.thinking);
  });

  const stallCases = [
    {
      name: "soft stall timer after stallSoftMs",
      delayMs: DEFAULT_TIMING.stallSoftMs,
      expected: DEFAULT_EMOJIS.stallSoft,
    },
    {
      name: "hard stall timer after stallHardMs",
      delayMs: DEFAULT_TIMING.stallHardMs,
      expected: DEFAULT_EMOJIS.stallHard,
    },
  ] as const;

  const createControllerAfterThinking = async () => {
    const state = createEnabledController();
    void state.controller.setThinking();
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);
    return state;
  };

  it.each(stallCases)("should trigger $name", async ({ delayMs, expected }) => {
    const { calls } = await createControllerAfterThinking();
    await vi.advanceTimersByTimeAsync(delayMs);

    expectSetEmojiCall(calls, expected);
  });

  const stallResetCases = [
    {
      name: "phase change",
      runUpdate: (controller: ReturnType<typeof createStatusReactionController>) => {
        void controller.setTool("exec");
        return vi.advanceTimersByTimeAsync(DEFAULT_TIMING.debounceMs);
      },
    },
    {
      name: "repeated same-phase updates",
      runUpdate: (controller: ReturnType<typeof createStatusReactionController>) => {
        void controller.setThinking();
        return Promise.resolve();
      },
    },
  ] as const;

  it.each(stallResetCases)("should reset stall timers on $name", async ({ runUpdate }) => {
    const { calls, controller } = await createControllerAfterThinking();

    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.stallSoftMs / 2);
    await runUpdate(controller);
    await vi.advanceTimersByTimeAsync(DEFAULT_TIMING.stallSoftMs / 2);

    const stallCalls = calls.filter((c) => c.emoji === DEFAULT_EMOJIS.stallSoft);
    expect(stallCalls).toHaveLength(0);
  });

  it("should call onError callback when adapter throws", async () => {
    const onError = vi.fn();
    const adapter: StatusReactionAdapter = {
      setReaction: vi.fn(async () => {
        throw new Error("Network error");
      }),
    };

    const controller = createStatusReactionController({
      enabled: true,
      adapter,
      initialEmoji: "��",
      onError,
    });

    void controller.setQueued();
    await vi.runAllTimersAsync();

    expect(onError).toHaveBeenCalled();
  });
});

describe("constants", () => {
  it("should export CODING_TOOL_TOKENS", () => {
    expectArrayContainsAll(CODING_TOOL_TOKENS, ["exec""read""write"]);
  });

  it("should export WEB_TOOL_TOKENS", () => {
    expectArrayContainsAll(WEB_TOOL_TOKENS, ["web_search""browser"]);
  });

  it("should export DEFAULT_EMOJIS with all required keys", () => {
    expectObjectHasKeys(DEFAULT_EMOJIS, [
      "queued",
      "thinking",
      "compacting",
      "tool",
      "coding",
      "web",
      "done",
      "error",
      "stallSoft",
      "stallHard",
    ]);
  });

  it("should export DEFAULT_TIMING with all required keys", () => {
    expectObjectHasKeys(DEFAULT_TIMING, [
      "debounceMs",
      "stallSoftMs",
      "stallHardMs",
      "doneHoldMs",
      "errorHoldMs",
    ]);
  });
});

Messung V0.5 in Prozent
C=97 H=100 G=98

¤ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet am  2026-05-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.