/** * Tests for bot.ts helper functions */ // Mock dependencies before importing bot jest.mock('../util', () => { const actual = jest.requireActual('../util'); return { ...actual, openDb: jest.fn(), db: { migrate: jest.fn(), get: jest.fn(), run: jest.fn(), }, }; }); jest.mock('node-fetch', () => jest.fn()); jest.mock('tmp', () => ({ fileSync: jest.fn(() => ({ name: '/tmp/test' })), setGracefulCleanup: jest.fn(), })); jest.mock('fs', () => ({ ...jest.requireActual('fs'), writeFileSync: jest.fn(), readFileSync: jest.fn(), existsSync: jest.fn(), })); // Import helper functions from shared module const { parseLoadingEmojis, getRandomLoadingEmoji, KAWAII_PHRASES, createStatusEmbed, } = require('../commands/helpers'); function formatLoadingMessage(emoji: string, reasoning: string): string { const phrase = KAWAII_PHRASES[Math.floor(Math.random() * KAWAII_PHRASES.length)]; let content = `${emoji}\n${phrase}`; if (reasoning && reasoning.trim().length > 0) { const displayReasoning = reasoning.length > 500 ? reasoning.slice(0, 500) + '...' : reasoning; content += `\n\n> ${displayReasoning}`; } return content; } describe('bot.ts helper functions', () => { /** * Convert a Date to a Discord snowflake ID (approximate) * Discord epoch: 2015-01-01T00:00:00.000Z */ function dateToSnowflake(date: Date): string { const DISCORD_EPOCH = 1420070400000n; const timestamp = BigInt(date.getTime()); const snowflake = (timestamp - DISCORD_EPOCH) << 22n; return snowflake.toString(); } describe('dateToSnowflake', () => { it('should convert Discord epoch to snowflake 0', () => { const discordEpoch = new Date('2015-01-01T00:00:00.000Z'); const result = dateToSnowflake(discordEpoch); expect(result).toBe('0'); }); it('should convert a known date to snowflake', () => { // Test with a known date const testDate = new Date('2024-01-01T00:00:00.000Z'); const result = dateToSnowflake(testDate); expect(result).toMatch(/^\d+$/); // Should be a numeric string expect(result.length).toBeGreaterThan(10); // Snowflakes are large numbers }); it('should produce increasing snowflakes for increasing dates', () => { const date1 = new Date('2024-01-01T00:00:00.000Z'); const date2 = new Date('2024-01-02T00:00:00.000Z'); const snowflake1 = dateToSnowflake(date1); const snowflake2 = dateToSnowflake(date2); expect(BigInt(snowflake2)).toBeGreaterThan(BigInt(snowflake1)); }); }); describe('textOnlyMessages', () => { function textOnlyMessages(message: { cleanContent: string; type: number }): boolean { const { MessageType } = require('discord.js'); return ( message.cleanContent.length > 0 && (message.type === MessageType.Default || message.type === MessageType.Reply) ); } it('should return true for messages with content and default type', () => { const mockMessage = { cleanContent: 'Hello!', type: 0, // Default }; expect(textOnlyMessages(mockMessage)).toBe(true); }); it('should return true for messages with content and reply type', () => { const mockMessage = { cleanContent: 'Reply!', type: 19, // Reply }; expect(textOnlyMessages(mockMessage)).toBe(true); }); it('should return false for empty messages', () => { const mockMessage = { cleanContent: '', type: 0, }; expect(textOnlyMessages(mockMessage)).toBe(false); }); it('should return false for system messages', () => { const mockMessage = { cleanContent: 'System message', type: 1, // RecipientAdd }; expect(textOnlyMessages(mockMessage)).toBe(false); }); }); describe('isGoodResponse', () => { const MAX_RESPONSE_LENGTH = 4000; function isGoodResponse(response: string): boolean { return response.length > 0 && response.length <= MAX_RESPONSE_LENGTH; } it('should return true for non-empty responses', () => { expect(isGoodResponse('Hello!')).toBe(true); expect(isGoodResponse('a')).toBe(true); }); it('should return false for empty responses', () => { expect(isGoodResponse('')).toBe(false); }); it('should return true for responses at exactly 4000 characters', () => { const response = 'a'.repeat(4000); expect(isGoodResponse(response)).toBe(true); }); it('should return false for responses exceeding 4000 characters', () => { const response = 'a'.repeat(4001); expect(isGoodResponse(response)).toBe(false); }); it('should return false for responses significantly exceeding 4000 characters', () => { const response = 'a'.repeat(5000); expect(isGoodResponse(response)).toBe(false); }); }); describe('parseLoadingEmojis', () => { it('should parse emojis from environment variable', () => { const original = process.env.LOADING_EMOJIS; process.env.LOADING_EMOJIS = '<:clueless:123>,,,'; const result = parseLoadingEmojis(); process.env.LOADING_EMOJIS = original; expect(result).toHaveLength(4); expect(result).toEqual([ '<:clueless:123>', '', '', '', ]); }); it('should return default emojis when LOADING_EMOJIS is empty', () => { const original = process.env.LOADING_EMOJIS; process.env.LOADING_EMOJIS = ''; const result = parseLoadingEmojis(); process.env.LOADING_EMOJIS = original; expect(result).toEqual(['🤔', '✨', '🎵']); }); it('should handle whitespace in emoji list', () => { const original = process.env.LOADING_EMOJIS; process.env.LOADING_EMOJIS = ' <:test:123> , '; const result = parseLoadingEmojis(); process.env.LOADING_EMOJIS = original; expect(result).toEqual(['<:test:123>', '']); }); }); describe('getRandomLoadingEmoji', () => { it('should return a valid emoji from the list', () => { const result = getRandomLoadingEmoji(); const validEmojis = parseLoadingEmojis(); expect(validEmojis).toContain(result); }); }); describe('formatLoadingMessage', () => { it('should format message with emoji and phrase only when no reasoning', () => { const result = formatLoadingMessage('<:clueless:123>', ''); expect(result).toContain('<:clueless:123>'); // Check that there's no blockquote (newline followed by "> ") expect(result).not.toMatch(/\n\n> /); }); it('should include reasoning in blockquote when present', () => { const reasoning = 'This is my thought process...'; const result = formatLoadingMessage('', reasoning); expect(result).toContain(''); expect(result).toContain(`> ${reasoning}`); }); it('should truncate long reasoning text', () => { const longReasoning = 'a'.repeat(600); const result = formatLoadingMessage('<:clueless:123>', longReasoning); expect(result).toContain('...'); expect(result.length).toBeLessThan(longReasoning.length + 50); }); }); });