Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
import { ACTIVE_RUN_STATUSES } from "@rakazo/core";
|
|
import { renderToString } from "react-dom/server";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
BotAvatar,
|
|
DEFAULT_GROK_BOT_COLOR,
|
|
GROK_BOT_COLORS,
|
|
GrokShapePreview,
|
|
parseBotAvatar,
|
|
resolvePersonaColorDef,
|
|
resolvePersonaShape,
|
|
} from "./bot-avatar.js";
|
|
|
|
describe("BotAvatar", () => {
|
|
it("renders distinct shape gradient IDs for concurrent working avatars", () => {
|
|
const html = renderToString(
|
|
<div>
|
|
<BotAvatar color="#8B5CF6" status="running" />
|
|
<BotAvatar color="#10B981" status="running" />
|
|
</div>,
|
|
);
|
|
|
|
const bodyGradIds = [...html.matchAll(/id="(evoo-body-[^"]+)"/g)].map((m) => m[1]);
|
|
expect(bodyGradIds).toHaveLength(2);
|
|
expect(new Set(bodyGradIds).size).toBe(2);
|
|
for (const id of bodyGradIds) {
|
|
expect(html).toContain(`url(#${id})`);
|
|
}
|
|
});
|
|
|
|
it.each([...ACTIVE_RUN_STATUSES])("marks active run status %s as working", (status) => {
|
|
const html = renderToString(<BotAvatar color="#3B82F6" status={status} />);
|
|
expect(html).toContain("<svg");
|
|
expect(html).toContain('data-working="true"');
|
|
});
|
|
|
|
it("keeps working attribute false when idle", () => {
|
|
const html = renderToString(<BotAvatar color="#F59E0B" status="idle" />);
|
|
expect(html).toContain('data-working="false"');
|
|
});
|
|
|
|
it("renders a geometric mascot for plain color values", () => {
|
|
const html = renderToString(
|
|
<BotAvatar color="#D9508A" identity="maya" size={28} status="running" />,
|
|
);
|
|
expect(html).toContain("<svg");
|
|
expect(html).toContain("<path");
|
|
expect(html).toContain("<ellipse");
|
|
expect(html).toContain('data-working="true"');
|
|
});
|
|
|
|
it("renders distinct shapes for distinct bot identities", () => {
|
|
const maya = renderToString(<BotAvatar color="#D9508A" identity="maya" />);
|
|
const github = renderToString(<BotAvatar color="#D9508A" identity="github" />);
|
|
expect(maya).not.toEqual(github);
|
|
});
|
|
|
|
it("parses shape indexes from encoded color values", () => {
|
|
const parsed = parseBotAvatar(`${DEFAULT_GROK_BOT_COLOR}::shape_3`);
|
|
expect(parsed.color).toBe(DEFAULT_GROK_BOT_COLOR);
|
|
expect(parsed.shapeIndex).toBe(3);
|
|
expect(parsed.isImage).toBe(false);
|
|
});
|
|
|
|
it("normalizes malformed shape suffixes to shape 0", () => {
|
|
expect(parseBotAvatar(`${DEFAULT_GROK_BOT_COLOR}::shape_-1`).shapeIndex).toBe(0);
|
|
expect(parseBotAvatar(`${DEFAULT_GROK_BOT_COLOR}::shape_3junk`).shapeIndex).toBe(0);
|
|
expect(parseBotAvatar(`${DEFAULT_GROK_BOT_COLOR}::shape_`).shapeIndex).toBe(0);
|
|
});
|
|
|
|
it("exposes the violet identity color as the shared default", () => {
|
|
expect(GROK_BOT_COLORS).toContain(DEFAULT_GROK_BOT_COLOR);
|
|
expect(parseBotAvatar(`${DEFAULT_GROK_BOT_COLOR}::shape_0`).color).toBe(DEFAULT_GROK_BOT_COLOR);
|
|
});
|
|
|
|
it("resolves explicit colors and shapes", () => {
|
|
expect(resolvePersonaColorDef("bot", "#10B981").hex.toLowerCase()).toBe("#10b981");
|
|
expect(resolvePersonaColorDef("bot", "#fff").hex).toBe("#fff");
|
|
expect(resolvePersonaShape("bot", "hex")).toContain("M");
|
|
expect(GROK_BOT_COLORS.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("falls back to the identity palette for invalid custom hex", () => {
|
|
expect(resolvePersonaColorDef("bot", "#zzzzzz")).toEqual(resolvePersonaColorDef("bot"));
|
|
expect(resolvePersonaColorDef("bot", "#ggg")).toEqual(resolvePersonaColorDef("bot"));
|
|
});
|
|
|
|
it("renders uploaded images without the geometric svg", () => {
|
|
const html = renderToString(
|
|
<BotAvatar color="data:image/png;base64,abc" identity="maya" size={32} />,
|
|
);
|
|
expect(html).toContain("<img");
|
|
expect(html).not.toContain("<path");
|
|
expect(html).not.toContain("grok-character-eyes");
|
|
});
|
|
|
|
it("does not treat arbitrary http(s) color values as remote images", () => {
|
|
const parsed = parseBotAvatar("https://evil.example/track.png");
|
|
expect(parsed.isImage).toBe(false);
|
|
expect(parsed.imageUrl).toBeUndefined();
|
|
const html = renderToString(
|
|
<BotAvatar color="https://evil.example/track.png" identity="maya" size={32} />,
|
|
);
|
|
expect(html).not.toContain("<img");
|
|
expect(html).not.toContain("evil.example");
|
|
});
|
|
|
|
it("honors reduced-motion for the working mascot pulse class", () => {
|
|
const html = renderToString(
|
|
<BotAvatar color="#8B5CF6" identity="maya" size={32} status="running" />,
|
|
);
|
|
expect(html).toContain("animate-pulse");
|
|
expect(html).toContain("motion-reduce:animate-none");
|
|
});
|
|
|
|
it("exposes shape picker name and pressed state", () => {
|
|
const html = renderToString(
|
|
<GrokShapePreview shapeIndex={0} color="#8B5CF6" selected onClick={() => undefined} />,
|
|
);
|
|
expect(html).toContain('aria-label="EvOo"');
|
|
expect(html).toContain('aria-pressed="true"');
|
|
expect(html).toContain("focus-visible:ring-2");
|
|
});
|
|
});
|