ai-functions
Version:
Core AI primitives for building intelligent applications
491 lines (490 loc) • 21.5 kB
JavaScript
/**
* Tests for core AI functions
*
* These tests verify the API contracts for each function.
* Tests marked with .skipIf(!hasGateway) require actual AI calls.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { stringify as yamlStringify } from 'yaml';
// Skip tests if no gateway configured
const hasGateway = !!process.env.AI_GATEWAY_URL || !!process.env.ANTHROPIC_API_KEY;
// ============================================================================
// Mock implementations for unit tests
// ============================================================================
// Mock generate function that all others should call
const mockGenerate = vi.fn();
// ============================================================================
// ai() - Direct text generation
// ============================================================================
describe('ai()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue('Generated text');
});
it('should accept a string prompt', async () => {
const ai = createMockAi();
const result = await ai('Write a haiku');
expect(mockGenerate).toHaveBeenCalledWith('text', 'Write a haiku', expect.any(Object));
expect(result).toBe('Generated text');
});
it('should accept tagged template literal', async () => {
const ai = createMockAi();
const topic = 'TypeScript';
const result = await ai `Write about ${topic}`;
expect(mockGenerate).toHaveBeenCalled();
const [, prompt] = mockGenerate.mock.calls[0];
expect(prompt).toContain('TypeScript');
expect(result).toBe('Generated text');
});
it('should accept options parameter', async () => {
const ai = createMockAi();
await ai `test`({ model: 'claude-opus-4-5' });
expect(mockGenerate).toHaveBeenCalledWith('text', 'test', expect.objectContaining({ model: 'claude-opus-4-5' }));
});
it('should return string type', async () => {
const ai = createMockAi();
const result = await ai('test');
expect(typeof result).toBe('string');
});
});
// ============================================================================
// summarize() - Condense text
// ============================================================================
describe('summarize()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue('Summary of content');
});
it('should accept text to summarize', async () => {
const summarize = createMockSummarize();
const result = await summarize `${longArticle}`;
expect(mockGenerate).toHaveBeenCalledWith('summary', expect.stringContaining('article'), expect.any(Object));
expect(result).toBe('Summary of content');
});
it('should support length option', async () => {
const summarize = createMockSummarize();
await summarize `${longArticle}`({ length: 'short' });
expect(mockGenerate).toHaveBeenCalledWith('summary', expect.any(String), expect.objectContaining({ length: 'short' }));
});
it('should support audience option', async () => {
const summarize = createMockSummarize();
await summarize `${technicalReport}${{ audience: 'executives', focus: 'business impact' }}`;
const [, prompt] = mockGenerate.mock.calls[0];
expect(prompt).toContain('executives');
expect(prompt).toContain('business impact');
});
});
// ============================================================================
// is() - Boolean classification
// ============================================================================
describe('is()', () => {
beforeEach(() => {
mockGenerate.mockReset();
});
it('should return boolean true', async () => {
mockGenerate.mockResolvedValue(true);
const is = createMockIs();
const result = await is `${'hello@example.com'} a valid email?`;
expect(result).toBe(true);
});
it('should return boolean false', async () => {
mockGenerate.mockResolvedValue(false);
const is = createMockIs();
const result = await is `${'not-an-email'} a valid email?`;
expect(result).toBe(false);
});
it('should accept natural question format', async () => {
mockGenerate.mockResolvedValue(true);
const is = createMockIs();
await is `${'The product is amazing!'} positive sentiment?`;
expect(mockGenerate).toHaveBeenCalledWith('boolean', expect.stringContaining('positive sentiment'), expect.any(Object));
});
it('should support model option for complex classifications', async () => {
mockGenerate.mockResolvedValue(true);
const is = createMockIs();
await is `${claim} factually accurate?`({ model: 'claude-opus-4-5' });
expect(mockGenerate).toHaveBeenCalledWith('boolean', expect.any(String), expect.objectContaining({ model: 'claude-opus-4-5' }));
});
});
// ============================================================================
// list() - Generate a list
// ============================================================================
describe('list()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue(['Item 1', 'Item 2', 'Item 3']);
});
it('should return an array of strings', async () => {
const list = createMockList();
const result = await list `startup ideas for ${industry}`;
expect(Array.isArray(result)).toBe(true);
expect(result).toHaveLength(3);
expect(result.every((item) => typeof item === 'string')).toBe(true);
});
it('should respect count in prompt', async () => {
const list = createMockList();
await list `10 blog post titles for ${topic}`;
expect(mockGenerate).toHaveBeenCalledWith('list', expect.stringContaining('10'), expect.any(Object));
});
it('should support count option', async () => {
const list = createMockList();
await list('startup ideas', { count: 10 });
expect(mockGenerate).toHaveBeenCalledWith('list', 'startup ideas', expect.objectContaining({ count: 10 }));
});
});
// ============================================================================
// lists() - Generate multiple named lists
// ============================================================================
describe('lists()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue({
pros: ['Pro 1', 'Pro 2'],
cons: ['Con 1', 'Con 2'],
});
});
it('should return named lists object', async () => {
const lists = createMockLists();
const result = await lists `pros and cons of ${topic}`;
expect(result).toHaveProperty('pros');
expect(result).toHaveProperty('cons');
expect(Array.isArray(result.pros)).toBe(true);
expect(Array.isArray(result.cons)).toBe(true);
});
it('should support SWOT analysis format', async () => {
mockGenerate.mockResolvedValue({
strengths: ['S1'],
weaknesses: ['W1'],
opportunities: ['O1'],
threats: ['T1'],
});
const lists = createMockLists();
const result = await lists `SWOT analysis for ${{ company, market }}`;
expect(result).toHaveProperty('strengths');
expect(result).toHaveProperty('weaknesses');
expect(result).toHaveProperty('opportunities');
expect(result).toHaveProperty('threats');
});
});
// ============================================================================
// extract() - Extract from text
// ============================================================================
describe('extract()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue(['John Smith', 'Jane Doe']);
});
it('should extract items from text', async () => {
const extract = createMockExtract();
const result = await extract `person names from ${article}`;
expect(Array.isArray(result)).toBe(true);
expect(result).toContain('John Smith');
expect(result).toContain('Jane Doe');
});
it('should support schema for structured extraction', async () => {
mockGenerate.mockResolvedValue([
{ name: 'Acme Corp', role: 'competitor' },
{ name: 'Beta Inc', role: 'partner' },
]);
const extract = createMockExtract();
const result = await extract `companies from ${text}${{
schema: {
name: 'Company name',
role: 'mentioned as: competitor | partner | customer',
},
}}`;
expect(result[0]).toHaveProperty('name');
expect(result[0]).toHaveProperty('role');
});
});
// ============================================================================
// write() - Generate text content
// ============================================================================
describe('write()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue('Generated content here...');
});
it('should generate text content', async () => {
const write = createMockWrite();
const result = await write `professional email to ${recipient} about ${subject}`;
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});
it('should support tone option', async () => {
const write = createMockWrite();
await write('blog post', { tone: 'casual', topic: 'TypeScript' });
expect(mockGenerate).toHaveBeenCalledWith('text', 'blog post', expect.objectContaining({ tone: 'casual' }));
});
it('should support length option', async () => {
const write = createMockWrite();
await write('article', { length: 'long' });
expect(mockGenerate).toHaveBeenCalledWith('text', 'article', expect.objectContaining({ length: 'long' }));
});
});
// ============================================================================
// code() - Generate code
// ============================================================================
describe('code()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue('function validate(email) { return email.includes("@"); }');
});
it('should generate code', async () => {
const code = createMockCode();
const result = await code `email validation function`;
expect(typeof result).toBe('string');
expect(result).toContain('function');
});
it('should support language option', async () => {
const code = createMockCode();
await code('REST API endpoints', { language: 'typescript' });
expect(mockGenerate).toHaveBeenCalledWith('code', 'REST API endpoints', expect.objectContaining({ language: 'typescript' }));
});
it('should handle complex requirements via object interpolation', async () => {
const code = createMockCode();
const requirements = {
pages: ['home', 'about', 'pricing'],
features: ['dark mode', 'animations'],
stack: 'Next.js + Tailwind',
};
await code `marketing website${{ requirements }}`;
const [, prompt] = mockGenerate.mock.calls[0];
expect(prompt).toContain('pages:');
expect(prompt).toContain('- home');
expect(prompt).toContain('features:');
});
});
// ============================================================================
// diagram() - Generate diagrams
// ============================================================================
describe('diagram()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue('graph TD\n A --> B\n B --> C');
});
it('should generate mermaid diagrams', async () => {
const diagram = createMockDiagram();
const result = await diagram `user authentication flow`;
expect(typeof result).toBe('string');
expect(result).toContain('graph');
});
it('should support format option', async () => {
const diagram = createMockDiagram();
await diagram('database schema', { format: 'mermaid', type: 'erd' });
expect(mockGenerate).toHaveBeenCalledWith('diagram', 'database schema', expect.objectContaining({ format: 'mermaid', type: 'erd' }));
});
});
// ============================================================================
// slides() - Generate presentations
// ============================================================================
describe('slides()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue('---\ntheme: default\n---\n\n# Slide 1\n\nContent here');
});
it('should generate slidev-format markdown', async () => {
const slides = createMockSlides();
const result = await slides `${topic}`;
expect(typeof result).toBe('string');
expect(result).toContain('---');
});
it('should support format option', async () => {
const slides = createMockSlides();
await slides('quarterly review', { format: 'marp', slides: 12 });
expect(mockGenerate).toHaveBeenCalledWith('slides', 'quarterly review', expect.objectContaining({ format: 'marp', slides: 12 }));
});
it('should support speaker notes', async () => {
const slides = createMockSlides();
await slides('workshop', { includeNotes: true, duration: '2 hours' });
expect(mockGenerate).toHaveBeenCalledWith('slides', 'workshop', expect.objectContaining({ includeNotes: true }));
});
});
// ============================================================================
// image() - Generate images
// ============================================================================
describe('image()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue(Buffer.from('fake-image-data'));
});
it('should generate image buffer', async () => {
const image = createMockImage();
const result = await image `minimalist logo for ${companyName}`;
expect(Buffer.isBuffer(result)).toBe(true);
});
it('should support size option', async () => {
const image = createMockImage();
await image('robot reading a book', { size: '1024x1024', style: 'cartoon' });
expect(mockGenerate).toHaveBeenCalledWith('image', 'robot reading a book', expect.objectContaining({ size: '1024x1024', style: 'cartoon' }));
});
});
// ============================================================================
// video() - Generate videos
// ============================================================================
describe('video()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue(Buffer.from('fake-video-data'));
});
it('should generate video buffer', async () => {
const video = createMockVideo();
const result = await video `product demo for ${productName}`;
expect(Buffer.isBuffer(result)).toBe(true);
});
it('should support duration and aspect options', async () => {
const video = createMockVideo();
await video('promotional video', { duration: 30, aspect: '16:9', style: 'motion graphics' });
expect(mockGenerate).toHaveBeenCalledWith('video', 'promotional video', expect.objectContaining({ duration: 30, aspect: '16:9' }));
});
});
// ============================================================================
// research() - Agentic research
// ============================================================================
describe('research()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue({
summary: 'Key findings...',
sources: [{ url: 'https://example.com', title: 'Source 1' }],
findings: ['Finding 1', 'Finding 2'],
confidence: 0.85,
});
});
it('should return structured research results', async () => {
const research = createMockResearch();
const result = await research `${topic}`;
expect(result).toHaveProperty('summary');
expect(result).toHaveProperty('sources');
expect(result).toHaveProperty('findings');
expect(result).toHaveProperty('confidence');
});
it('should support depth option', async () => {
const research = createMockResearch();
await research `market size for AI tools`({ depth: 'thorough' });
expect(mockGenerate).toHaveBeenCalledWith('research', expect.any(String), expect.objectContaining({ depth: 'thorough' }));
});
});
// ============================================================================
// do() - Single-pass task with tools
// ============================================================================
describe('do()', () => {
beforeEach(() => {
mockGenerate.mockReset();
mockGenerate.mockResolvedValue({ summary: 'Done', result: 'Task completed' });
});
it('should execute a task', async () => {
const doFn = createMockDo();
const result = await doFn `translate ${text} to Spanish`;
expect(result).toBeDefined();
});
it('should handle complex multi-function tasks', async () => {
mockGenerate.mockResolvedValue({
summary: 'Article summary',
people: ['John', 'Jane'],
actionItems: ['Review', 'Follow up'],
});
const doFn = createMockDo();
const result = await doFn `
analyze this article and give me a summary,
key people mentioned, and action items
${article}
`;
expect(result).toHaveProperty('summary');
});
it('is single-pass, not agentic loop', async () => {
const doFn = createMockDo();
await doFn `analyze ${data}`;
// Should only call generate once (single pass)
expect(mockGenerate).toHaveBeenCalledTimes(1);
});
});
// ============================================================================
// Helper functions to create mock implementations
// ============================================================================
function createMockAi() {
return createMockFunction('text');
}
function createMockSummarize() {
return createMockFunction('summary');
}
function createMockIs() {
return createMockFunction('boolean');
}
function createMockList() {
return createMockFunction('list');
}
function createMockLists() {
return createMockFunction('lists');
}
function createMockExtract() {
return createMockFunction('extract');
}
function createMockWrite() {
return createMockFunction('text');
}
function createMockCode() {
return createMockFunction('code');
}
function createMockDiagram() {
return createMockFunction('diagram');
}
function createMockSlides() {
return createMockFunction('slides');
}
function createMockImage() {
return createMockFunction('image');
}
function createMockVideo() {
return createMockFunction('video');
}
function createMockResearch() {
return createMockFunction('research');
}
function createMockDo() {
return createMockFunction('do');
}
function createMockFunction(type) {
return function (promptOrStrings, ...args) {
let prompt;
if (Array.isArray(promptOrStrings) && 'raw' in promptOrStrings) {
// Tagged template
prompt = promptOrStrings.reduce((acc, str, i) => {
const value = args[i];
if (value === undefined)
return acc + str;
if (typeof value === 'object' && value !== null) {
// Convert objects to YAML for readability (matches real implementation)
return acc + str + '\n' + yamlStringify(value).trim();
}
return acc + str + String(value);
}, '');
// Return chainable for options - properly make it thenable
const basePromise = mockGenerate(type, prompt, {});
const chainable = (options) => mockGenerate(type, prompt, options || {});
chainable.then = basePromise.then.bind(basePromise);
chainable.catch = basePromise.catch.bind(basePromise);
return chainable;
}
// Regular call
prompt = promptOrStrings;
return mockGenerate(type, prompt, args[0] || {});
};
}
// ============================================================================
// Test fixtures
// ============================================================================
const longArticle = 'This is a long article about technology and innovation...';
const technicalReport = 'Technical analysis of system performance metrics...';
const industry = 'fintech';
const topic = 'TypeScript';
const claim = 'The Earth is round';
const article = 'Article mentioning John Smith and Jane Doe...';
const text = 'Some text content';
const company = 'Acme Corp';
const market = 'SaaS';
const recipient = 'John';
const subject = 'Project Update';
const companyName = 'TechCorp';
const productName = 'ProductX';
const data = { key: 'value' };