The mobile composer/footer slid over the message list when the user swiped with the keyboard up. Cause: --vv-height / --vv-offset-top were routed through React state via useMobileKeyboard, so on iOS — which fires visualViewport scroll/resize on the same frame as its keyboard animation — the .chat-thread translation lagged by one paint, visible as the composer momentarily floating over messages. Now those two vars are written imperatively in a useLayoutEffect directly to the .chat-thread DOM node on every visualViewport event, mirroring the working pattern at QuickChatFAB.tsx:1032-1052 (which already works correctly on mobile). Only --keyboard-overlap (a structural open/close signal, not per-frame) still flows through React state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
4.4 KiB
JavaScript
114 lines
4.4 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
const { mockResolveCli, mockInstallFusionSkill } = vi.hoisted(() => ({
|
|
mockResolveCli: vi.fn().mockReturnValue({
|
|
binaryPath: "hermes",
|
|
model: undefined,
|
|
provider: undefined,
|
|
maxTurns: 12,
|
|
yolo: false,
|
|
cliTimeoutMs: 300_000,
|
|
}),
|
|
mockInstallFusionSkill: vi.fn().mockReturnValue({
|
|
outcome: "installed",
|
|
sourceDir: "/tmp/source",
|
|
targetDir: "/tmp/target",
|
|
}),
|
|
}));
|
|
vi.mock("../cli-spawn.js", async () => {
|
|
const actual = await vi.importActual("../cli-spawn.js");
|
|
return {
|
|
...actual,
|
|
resolveCliSettings: mockResolveCli,
|
|
};
|
|
});
|
|
vi.mock("../fusion-skill-install.js", async () => {
|
|
const actual = await vi.importActual("../fusion-skill-install.js");
|
|
return {
|
|
...actual,
|
|
installFusionSkillIntoHermesHome: mockInstallFusionSkill,
|
|
};
|
|
});
|
|
import plugin, { HERMES_RUNTIME_ID, hermesRuntimeFactory, hermesRuntimeMetadata } from "../index.js";
|
|
import { HermesRuntimeAdapter } from "../runtime-adapter.js";
|
|
function createMockContext(settings = {}) {
|
|
return {
|
|
pluginId: "fusion-plugin-hermes-runtime",
|
|
settings,
|
|
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
|
emitEvent: vi.fn(),
|
|
taskStore: { getTask: vi.fn() },
|
|
};
|
|
}
|
|
describe("hermes-runtime plugin", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockResolveCli.mockReturnValue({
|
|
binaryPath: "hermes",
|
|
model: undefined,
|
|
provider: undefined,
|
|
maxTurns: 12,
|
|
yolo: false,
|
|
cliTimeoutMs: 300_000,
|
|
profile: undefined,
|
|
});
|
|
mockInstallFusionSkill.mockReturnValue({
|
|
outcome: "installed",
|
|
sourceDir: "/tmp/source",
|
|
targetDir: "/tmp/target",
|
|
});
|
|
});
|
|
it("has expected manifest identity", () => {
|
|
expect(plugin.manifest.id).toBe("fusion-plugin-hermes-runtime");
|
|
expect(plugin.manifest.name).toBe("Hermes Runtime Plugin");
|
|
expect(plugin.state).toBe("installed");
|
|
});
|
|
it("registers runtime metadata and exports matching constants", () => {
|
|
expect(HERMES_RUNTIME_ID).toBe("hermes");
|
|
expect(plugin.runtime?.metadata.runtimeId).toBe("hermes");
|
|
expect(plugin.runtime?.metadata.name).toBe("Hermes Runtime");
|
|
expect(plugin.runtime?.metadata.description).toContain("hermes");
|
|
expect(plugin.manifest.runtime).toEqual(hermesRuntimeMetadata);
|
|
});
|
|
it("onLoad logs selected binary path & model and emits loaded event", async () => {
|
|
mockResolveCli.mockReturnValue({
|
|
binaryPath: "/opt/homebrew/bin/hermes",
|
|
model: "claude-sonnet-4-5",
|
|
provider: "anthropic",
|
|
maxTurns: 12,
|
|
yolo: false,
|
|
cliTimeoutMs: 300_000,
|
|
profile: undefined,
|
|
});
|
|
const ctx = createMockContext({ binaryPath: "/opt/homebrew/bin/hermes" });
|
|
await plugin.hooks.onLoad(ctx);
|
|
expect(mockResolveCli).toHaveBeenCalledWith(ctx.settings);
|
|
expect(mockInstallFusionSkill).toHaveBeenCalledWith({ profile: undefined });
|
|
expect(ctx.logger.info).toHaveBeenCalledWith(expect.stringContaining("fusionSkill=installed"));
|
|
expect(ctx.emitEvent).toHaveBeenCalledWith("hermes-runtime:loaded", {
|
|
runtimeId: "hermes",
|
|
version: plugin.manifest.version,
|
|
});
|
|
});
|
|
it("onLoad warns but continues when skill install warns", async () => {
|
|
mockInstallFusionSkill.mockReturnValue({
|
|
outcome: "warning",
|
|
sourceDir: null,
|
|
targetDir: "/tmp/target",
|
|
reason: "missing skill source",
|
|
});
|
|
const ctx = createMockContext();
|
|
await plugin.hooks.onLoad(ctx);
|
|
expect(ctx.logger.warn).toHaveBeenCalledWith(expect.stringContaining("auto-install warning"));
|
|
expect(ctx.emitEvent).toHaveBeenCalledWith("hermes-runtime:loaded", {
|
|
runtimeId: "hermes",
|
|
version: plugin.manifest.version,
|
|
});
|
|
});
|
|
it("factory returns a HermesRuntimeAdapter", async () => {
|
|
const ctx = createMockContext({ binaryPath: "hermes" });
|
|
const runtime = (await hermesRuntimeFactory(ctx));
|
|
expect(runtime).toBeInstanceOf(HermesRuntimeAdapter);
|
|
expect(runtime.id).toBe("hermes");
|
|
});
|
|
});
|
|
//# sourceMappingURL=index.test.js.map
|