- Single-screen chat interface with Timmy's sovereign AI personality - Text messaging with real-time AI responses via server chat API - Voice recording and playback with waveform visualization - Image sharing (camera + photo library) with full-screen viewer - File attachments via document picker - Dark arcane theme matching the Timmy Time dashboard - Custom app icon with glowing T circuit design - Timmy system prompt ported from dashboard prompts.py - Unit tests for chat utilities and message types
136 lines
3.5 KiB
TypeScript
136 lines
3.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
// Test the utility functions from chat-store
|
|
// We can't directly test React hooks here, but we can test the pure functions
|
|
|
|
describe("formatBytes", () => {
|
|
// Re-implement locally since the module uses React context
|
|
function formatBytes(bytes: number): string {
|
|
if (bytes === 0) return "0 B";
|
|
const k = 1024;
|
|
const sizes = ["B", "KB", "MB", "GB"];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
}
|
|
|
|
it("formats 0 bytes", () => {
|
|
expect(formatBytes(0)).toBe("0 B");
|
|
});
|
|
|
|
it("formats bytes", () => {
|
|
expect(formatBytes(500)).toBe("500 B");
|
|
});
|
|
|
|
it("formats kilobytes", () => {
|
|
expect(formatBytes(1024)).toBe("1 KB");
|
|
expect(formatBytes(1536)).toBe("1.5 KB");
|
|
});
|
|
|
|
it("formats megabytes", () => {
|
|
expect(formatBytes(1048576)).toBe("1 MB");
|
|
expect(formatBytes(5242880)).toBe("5 MB");
|
|
});
|
|
});
|
|
|
|
describe("formatDuration", () => {
|
|
function formatDuration(seconds: number): string {
|
|
const m = Math.floor(seconds / 60);
|
|
const s = Math.floor(seconds % 60);
|
|
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
it("formats zero seconds", () => {
|
|
expect(formatDuration(0)).toBe("0:00");
|
|
});
|
|
|
|
it("formats seconds only", () => {
|
|
expect(formatDuration(45)).toBe("0:45");
|
|
});
|
|
|
|
it("formats minutes and seconds", () => {
|
|
expect(formatDuration(125)).toBe("2:05");
|
|
});
|
|
|
|
it("formats exact minutes", () => {
|
|
expect(formatDuration(60)).toBe("1:00");
|
|
});
|
|
});
|
|
|
|
describe("ChatMessage type structure", () => {
|
|
it("creates a valid text message", () => {
|
|
const msg = {
|
|
id: "msg_1",
|
|
role: "user" as const,
|
|
contentType: "text" as const,
|
|
text: "Hello Timmy",
|
|
timestamp: Date.now(),
|
|
};
|
|
expect(msg.role).toBe("user");
|
|
expect(msg.contentType).toBe("text");
|
|
expect(msg.text).toBe("Hello Timmy");
|
|
});
|
|
|
|
it("creates a valid image message", () => {
|
|
const msg = {
|
|
id: "msg_2",
|
|
role: "user" as const,
|
|
contentType: "image" as const,
|
|
uri: "file:///photo.jpg",
|
|
fileName: "photo.jpg",
|
|
mimeType: "image/jpeg",
|
|
timestamp: Date.now(),
|
|
};
|
|
expect(msg.contentType).toBe("image");
|
|
expect(msg.mimeType).toBe("image/jpeg");
|
|
});
|
|
|
|
it("creates a valid voice message", () => {
|
|
const msg = {
|
|
id: "msg_3",
|
|
role: "user" as const,
|
|
contentType: "voice" as const,
|
|
uri: "file:///voice.m4a",
|
|
duration: 5.2,
|
|
mimeType: "audio/m4a",
|
|
timestamp: Date.now(),
|
|
};
|
|
expect(msg.contentType).toBe("voice");
|
|
expect(msg.duration).toBe(5.2);
|
|
});
|
|
|
|
it("creates a valid file message", () => {
|
|
const msg = {
|
|
id: "msg_4",
|
|
role: "user" as const,
|
|
contentType: "file" as const,
|
|
uri: "file:///document.pdf",
|
|
fileName: "document.pdf",
|
|
fileSize: 1048576,
|
|
mimeType: "application/pdf",
|
|
timestamp: Date.now(),
|
|
};
|
|
expect(msg.contentType).toBe("file");
|
|
expect(msg.fileSize).toBe(1048576);
|
|
});
|
|
|
|
it("creates a valid assistant message", () => {
|
|
const msg = {
|
|
id: "msg_5",
|
|
role: "assistant" as const,
|
|
contentType: "text" as const,
|
|
text: "Sir, affirmative.",
|
|
timestamp: Date.now(),
|
|
};
|
|
expect(msg.role).toBe("assistant");
|
|
});
|
|
});
|
|
|
|
describe("Timmy system prompt", () => {
|
|
const TIMMY_SYSTEM_PROMPT = `You are Timmy — a sovereign AI agent.`;
|
|
|
|
it("contains Timmy identity", () => {
|
|
expect(TIMMY_SYSTEM_PROMPT).toContain("Timmy");
|
|
expect(TIMMY_SYSTEM_PROMPT).toContain("sovereign");
|
|
});
|
|
});
|