426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
302 lines
8.3 KiB
TypeScript
302 lines
8.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
/**
|
|
* Behavioral coverage for the exported 3D projection sub-component
|
|
* (VectorGraph3D from src/VectorBrowserView.tsx).
|
|
*
|
|
* three.js is real WebGL — there is no GL context in jsdom — so the component
|
|
* exposes a `createRenderer` prop seam (default:
|
|
* getBootConfig().companionVectorBrowser.createVectorBrowserRenderer). These
|
|
* tests drive that seam directly:
|
|
* (a) <2 embedded memories -> the "Not enough embeddings" empty state;
|
|
* (b) >=2 real (dim_768) embeddings + a fake WebGLRenderer (canvas domElement,
|
|
* noop setSize/setPixelRatio/render/dispose) -> the populated projection
|
|
* header + a per-type color legend + a mounted canvas;
|
|
* (c) createRenderer rejects -> the "Renderer unavailable" fallback.
|
|
*
|
|
* The THREE namespace is supplied via getBootConfig().companionVectorBrowser as
|
|
* a minimal stub covering exactly the API the scene-setup path constructs
|
|
* (Scene/Color/PerspectiveCamera/Raycaster/Vector2/SphereGeometry/
|
|
* MeshBasicMaterial/Mesh/GridHelper/BufferGeometry/BufferAttribute/
|
|
* LineBasicMaterial/LineSegments). The PCA projection itself runs for real via
|
|
* the un-mocked vector-browser-utils.
|
|
*/
|
|
|
|
import {
|
|
type MemoryRecord,
|
|
rowToMemory,
|
|
} from "@elizaos/ui/components/pages/vector-browser-utils";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
// ── Minimal THREE stub: only what the scene-setup path constructs ───────────
|
|
const threeStub = vi.hoisted(() => {
|
|
class Color {
|
|
getHex() {
|
|
return 0x333333;
|
|
}
|
|
}
|
|
class Vector2 {
|
|
set() {}
|
|
}
|
|
class Vector3 {
|
|
setScalar() {}
|
|
}
|
|
class Scene {
|
|
background: unknown = null;
|
|
add() {}
|
|
}
|
|
class PerspectiveCamera {
|
|
aspect = 1;
|
|
position = { set: () => {}, x: 0, y: 0, z: 0 };
|
|
lookAt() {}
|
|
updateProjectionMatrix() {}
|
|
}
|
|
class Raycaster {
|
|
setFromCamera() {}
|
|
intersectObjects() {
|
|
return [] as unknown[];
|
|
}
|
|
}
|
|
class SphereGeometry {
|
|
dispose() {}
|
|
}
|
|
class MeshBasicMaterial {
|
|
opacity = 1;
|
|
dispose() {}
|
|
}
|
|
class Mesh {
|
|
material: unknown;
|
|
userData: Record<string, unknown> = {};
|
|
position = { set: () => {} };
|
|
scale = new Vector3();
|
|
constructor(_g: unknown, m: unknown) {
|
|
this.material = m;
|
|
}
|
|
}
|
|
class GridHelper {
|
|
position = { y: 0 };
|
|
material: { dispose: () => void } = { dispose: () => {} };
|
|
geometry = { dispose: () => {} };
|
|
}
|
|
class BufferGeometry {
|
|
setAttribute() {}
|
|
dispose() {}
|
|
}
|
|
class BufferAttribute {}
|
|
class LineBasicMaterial {
|
|
dispose() {}
|
|
}
|
|
class LineSegments {}
|
|
return {
|
|
Color,
|
|
Vector2,
|
|
Vector3,
|
|
Scene,
|
|
PerspectiveCamera,
|
|
Raycaster,
|
|
SphereGeometry,
|
|
MeshBasicMaterial,
|
|
Mesh,
|
|
GridHelper,
|
|
BufferGeometry,
|
|
BufferAttribute,
|
|
LineBasicMaterial,
|
|
LineSegments,
|
|
};
|
|
});
|
|
|
|
function translate(key: string, vars?: Record<string, unknown>): string {
|
|
if (vars && typeof vars.defaultValue === "string") return vars.defaultValue;
|
|
return key;
|
|
}
|
|
|
|
type MockAppState = {
|
|
t: typeof translate;
|
|
};
|
|
|
|
vi.mock("@elizaos/ui/state", () => {
|
|
const state: MockAppState = { t: translate };
|
|
return {
|
|
useApp: () => state,
|
|
useAppSelector: <T,>(selector: (state: MockAppState) => T) =>
|
|
selector(state),
|
|
};
|
|
});
|
|
|
|
vi.mock("@elizaos/ui/config", () => ({
|
|
getBootConfig: () => ({
|
|
companionVectorBrowser: {
|
|
THREE: threeStub,
|
|
createVectorBrowserRenderer: async () => {
|
|
throw new Error("default renderer unavailable");
|
|
},
|
|
},
|
|
}),
|
|
}));
|
|
|
|
// The remaining @elizaos/ui glue subpaths are pulled in transitively by the
|
|
// view module; supply thin stand-ins so the module evaluates.
|
|
vi.mock("@elizaos/ui/agent-surface", () => ({
|
|
useAgentElement: () => ({ ref: { current: null }, agentProps: {} }),
|
|
}));
|
|
vi.mock("@elizaos/ui/api", () => ({ client: {} }));
|
|
vi.mock("@elizaos/ui/hooks", () => ({ useRenderGuard: () => {} }));
|
|
vi.mock("@elizaos/ui/layouts", () => ({
|
|
WorkspaceLayout: ({ children }: { children: React.ReactNode }) => (
|
|
<div>{children}</div>
|
|
),
|
|
}));
|
|
vi.mock("@elizaos/ui/components/composites/page-panel", () => ({
|
|
PagePanel: ({ children }: { children: React.ReactNode }) => (
|
|
<div>{children}</div>
|
|
),
|
|
}));
|
|
vi.mock("@elizaos/ui/components/ui/skeleton-layouts", () => ({
|
|
ListSkeleton: () => <div />,
|
|
}));
|
|
vi.mock("@elizaos/ui/components/ui/button", () => ({
|
|
Button: ({ children }: { children?: React.ReactNode }) => (
|
|
<button type="button">{children}</button>
|
|
),
|
|
}));
|
|
vi.mock("@elizaos/ui/components/ui/input", () => ({ Input: () => <input /> }));
|
|
vi.mock("@elizaos/ui/components/ui/select", () => ({
|
|
Select: ({ children }: { children?: React.ReactNode }) => (
|
|
<div>{children}</div>
|
|
),
|
|
SelectContent: ({ children }: { children?: React.ReactNode }) => (
|
|
<>{children}</>
|
|
),
|
|
SelectItem: () => null,
|
|
SelectTrigger: () => null,
|
|
SelectValue: () => null,
|
|
}));
|
|
vi.mock("@elizaos/ui/components/pages/MemoryDetailPanel", () => ({
|
|
MemoryDetailPanel: () => <div data-testid="detail" />,
|
|
}));
|
|
|
|
const { render, screen, cleanup, waitFor } = await import(
|
|
"@testing-library/react"
|
|
);
|
|
const { VectorGraph3D } = await import("../src/VectorBrowserView.tsx");
|
|
|
|
// ── Fixtures: real-shaped memories with parseable dim_768 embeddings ────────
|
|
function embeddedMemory(id: string, type: string, seed: number): MemoryRecord {
|
|
const parts: number[] = [];
|
|
for (let i = 0; i < 768; i += 1) {
|
|
parts.push(Number(((seed + 1) * 0.013 + i * 0.0007).toFixed(6)));
|
|
}
|
|
return rowToMemory({
|
|
id,
|
|
content: JSON.stringify({ text: `memory ${id}` }),
|
|
type,
|
|
room_id: "r",
|
|
entity_id: "e",
|
|
created_at: "2026-06-16T10:30:00.000Z",
|
|
unique: true,
|
|
dim_768: `[${parts.join(",")}]`,
|
|
});
|
|
}
|
|
|
|
function bareMemory(id: string): MemoryRecord {
|
|
return rowToMemory({
|
|
id,
|
|
content: "no vector",
|
|
type: "message",
|
|
room_id: "r",
|
|
entity_id: "e",
|
|
created_at: "2026-06-16T10:30:00.000Z",
|
|
unique: false,
|
|
dim_768: null,
|
|
});
|
|
}
|
|
|
|
function fakeRenderer() {
|
|
const canvas = document.createElement("canvas");
|
|
return {
|
|
domElement: canvas,
|
|
setSize: vi.fn(),
|
|
setPixelRatio: vi.fn(),
|
|
render: vi.fn(),
|
|
dispose: vi.fn(),
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
describe("VectorGraph3D", () => {
|
|
it("renders the empty state with fewer than 2 embedded memories", () => {
|
|
render(
|
|
<VectorGraph3D
|
|
memories={[embeddedMemory("m-0", "fact", 0), bareMemory("m-1")]}
|
|
onSelect={() => {}}
|
|
/>,
|
|
);
|
|
// only 1 memory has an embedding -> "Not enough embeddings" (bare i18n key)
|
|
expect(
|
|
screen.getByText("vectorbrowserview.NotEnoughEmbedding1"),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it("renders the populated projection header, canvas, and per-type color legend", async () => {
|
|
const renderer = fakeRenderer();
|
|
const createRenderer = vi.fn(async () => renderer as never);
|
|
|
|
const { container } = render(
|
|
<VectorGraph3D
|
|
memories={[
|
|
embeddedMemory("m-0", "fact", 0),
|
|
embeddedMemory("m-1", "message", 1),
|
|
embeddedMemory("m-2", "fact", 2),
|
|
]}
|
|
onSelect={() => {}}
|
|
createRenderer={createRenderer}
|
|
/>,
|
|
);
|
|
|
|
// header: "{N} {t('vectorbrowserview.vectorsProjectedTo1')}" -> bare key
|
|
expect(
|
|
screen.getByText(/vectorbrowserview\.vectorsProjectedTo1/),
|
|
).toBeTruthy();
|
|
// NOT the empty state
|
|
expect(
|
|
screen.queryByText("vectorbrowserview.NotEnoughEmbedding1"),
|
|
).toBeNull();
|
|
|
|
// the renderer seam was invoked and its canvas mounted into the container
|
|
await waitFor(() => {
|
|
expect(createRenderer).toHaveBeenCalled();
|
|
});
|
|
await waitFor(() => {
|
|
expect(container.querySelector("canvas")).toBe(renderer.domElement);
|
|
});
|
|
|
|
// color legend: one entry per distinct type (fact, message)
|
|
expect(screen.getByText("fact")).toBeTruthy();
|
|
expect(screen.getByText("message")).toBeTruthy();
|
|
});
|
|
|
|
it("renders the 'Renderer unavailable' fallback when createRenderer rejects", async () => {
|
|
const createRenderer = vi.fn(async () => {
|
|
throw new Error("WebGL init failed");
|
|
});
|
|
|
|
render(
|
|
<VectorGraph3D
|
|
memories={[
|
|
embeddedMemory("m-0", "fact", 0),
|
|
embeddedMemory("m-1", "message", 1),
|
|
]}
|
|
onSelect={() => {}}
|
|
createRenderer={createRenderer}
|
|
/>,
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Unavailable")).toBeTruthy();
|
|
});
|
|
});
|
|
});
|