Skip to main content
Glama

Scratchpad MCP

by pc035860
block-parser.test.ts12.4 kB
/** * BlockParser Class Unit Tests * * Comprehensive tests for the BlockParser utility class, * covering all block parsing operations including new/old format compatibility, * boundary conditions, and edge cases. */ import { describe, it, expect } from 'vitest'; import { BlockParser } from '../src/utils/BlockParser.js'; describe('BlockParser Unit Tests', () => { describe('parseBlocks - Format Detection', () => { it('should parse empty content', () => { expect(BlockParser.parseBlocks('')).toEqual([]); // 空白字符內容應該被視為單一 block const whitespaceResult = BlockParser.parseBlocks(' '); expect(whitespaceResult).toHaveLength(1); expect(whitespaceResult[0].content).toBe(' '); }); it('should parse single block with no splitter', () => { const content = 'Single block content\nWith multiple lines\nBut no splitter'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0]).toEqual({ content: content, index: 0, startPosition: 0, endPosition: content.length, isFirstBlock: true }); }); it('should parse content with new format splitter', () => { const content = 'First block\n\n---\n<!--- block start --->\nSecond block\n\n---\n<!--- block start --->\nThird block'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe('First block'); expect(blocks[0].isFirstBlock).toBe(true); expect(blocks[1].content).toBe('Second block'); expect(blocks[1].isFirstBlock).toBe(false); expect(blocks[2].content).toBe('Third block'); expect(blocks[2].isFirstBlock).toBe(false); }); it('should parse content with old format splitter', () => { const content = 'First block\n\n---\nSecond block\n\n---\nThird block'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe('First block'); expect(blocks[1].content).toBe('Second block'); expect(blocks[2].content).toBe('Third block'); }); it('should handle mixed new and old format splitters', () => { const content = 'First block\n\n---\nSecond block\n\n---\n<!--- block start --->\nThird block'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe('First block'); expect(blocks[1].content).toBe('Second block'); expect(blocks[2].content).toBe('Third block'); }); it('should prioritize new format over old format when overlapping', () => { // This tests the removeDuplicatePositions logic const content = 'First\n\n---\n<!--- block start --->\nSecond'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(2); expect(blocks[0].content).toBe('First'); expect(blocks[1].content).toBe('Second'); }); }); describe('parseBlocks - Position Tracking', () => { it('should correctly track block positions', () => { const content = 'Block1\n\n---\nBlock2\n\n---\nBlock3'; const blocks = BlockParser.parseBlocks(content); expect(blocks[0].startPosition).toBe(0); expect(blocks[0].endPosition).toBe(6); // 'Block1'.length expect(blocks[1].startPosition).toBe(12); // After '\n\n---\n' (splitter length 6) expect(blocks[1].endPosition).toBe(18); // 'Block2'.length + start expect(blocks[2].startPosition).toBe(24); // After second splitter }); it('should handle blocks with empty content', () => { const content = '\n\n---\n\n\n---\nFinal block'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe(''); expect(blocks[1].content).toBe(''); expect(blocks[2].content).toBe('Final block'); }); }); describe('getBlockRange - Basic Operations', () => { const sampleContent = 'Block1\n\n---\nBlock2\n\n---\nBlock3\n\n---\nBlock4'; it('should get specified blocks from end (fromEnd=true)', () => { const result = BlockParser.getBlockRange(sampleContent, 2, true); expect(result).toContain('Block3'); expect(result).toContain('Block4'); expect(result).not.toContain('Block1'); expect(result).not.toContain('Block2'); }); it('should get specified blocks from start (fromEnd=false)', () => { const result = BlockParser.getBlockRange(sampleContent, 2, false); expect(result).toContain('Block1'); expect(result).toContain('Block2'); expect(result).not.toContain('Block3'); expect(result).not.toContain('Block4'); }); it('should return all content when requesting more blocks than available', () => { const result = BlockParser.getBlockRange(sampleContent, 10, true); expect(result).toBe(sampleContent); }); it('should return empty string when requesting 0 blocks', () => { const result = BlockParser.getBlockRange(sampleContent, 0, true); expect(result).toBe(''); }); it('should handle negative block count gracefully', () => { const result = BlockParser.getBlockRange(sampleContent, -1, true); expect(result).toBe(''); }); it('should preserve original splitter format in reconstruction', () => { const newFormatContent = 'Block1\n\n---\n<!--- block start --->\nBlock2'; const result = BlockParser.getBlockRange(newFormatContent, 2, true); expect(result).toContain('<!--- block start --->'); const oldFormatContent = 'Block1\n\n---\nBlock2'; const result2 = BlockParser.getBlockRange(oldFormatContent, 2, true); expect(result2).not.toContain('<!--- block start --->'); expect(result2).toContain('---'); }); }); describe('chopBlocks - Block Removal', () => { const sampleContent = 'Block1\n\n---\nBlock2\n\n---\nBlock3\n\n---\nBlock4'; it('should remove specified number of blocks from end', () => { const result = BlockParser.chopBlocks(sampleContent, 1); expect(result).toContain('Block1'); expect(result).toContain('Block2'); expect(result).toContain('Block3'); expect(result).not.toContain('Block4'); }); it('should remove multiple blocks from end', () => { const result = BlockParser.chopBlocks(sampleContent, 2); expect(result).toContain('Block1'); expect(result).toContain('Block2'); expect(result).not.toContain('Block3'); expect(result).not.toContain('Block4'); }); it('should return empty string when chopping all blocks', () => { const result = BlockParser.chopBlocks(sampleContent, 4); expect(result).toBe(''); }); it('should return empty string when chopping more blocks than available', () => { const result = BlockParser.chopBlocks(sampleContent, 10); expect(result).toBe(''); }); it('should return original content when chopping 0 blocks', () => { const result = BlockParser.chopBlocks(sampleContent, 0); expect(result).toBe(sampleContent); }); it('should handle single block content', () => { const singleBlock = 'Only one block'; const result = BlockParser.chopBlocks(singleBlock, 1); expect(result).toBe(''); }); }); describe('getBlockCount - Block Counting', () => { it('should count blocks correctly with no splitters', () => { const content = 'Single block'; expect(BlockParser.getBlockCount(content)).toBe(1); }); it('should count blocks correctly with splitters', () => { const content = 'Block1\n\n---\nBlock2\n\n---\nBlock3'; expect(BlockParser.getBlockCount(content)).toBe(3); }); it('should count blocks correctly with mixed splitter formats', () => { const content = 'Block1\n\n---\nBlock2\n\n---\n<!--- block start --->\nBlock3'; expect(BlockParser.getBlockCount(content)).toBe(3); }); it('should return 0 for empty content', () => { expect(BlockParser.getBlockCount('')).toBe(0); expect(BlockParser.getBlockCount(' ')).toBe(1); // Whitespace still counts as content }); }); describe('Edge Cases and Boundary Conditions', () => { it('should handle content ending with splitter', () => { const content = 'Block1\n\n---\nBlock2\n\n---\n<!--- block start --->\n'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[2].content).toBe(''); // Last block is empty }); it('should handle content starting with splitter', () => { const content = '\n\n---\nBlock1\n\n---\nBlock2'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe(''); // First block is empty expect(blocks[1].content).toBe('Block1'); expect(blocks[2].content).toBe('Block2'); }); it('should handle consecutive splitters', () => { const content = 'Block1\n\n---\n\n\n---\nBlock3'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe('Block1'); expect(blocks[1].content).toBe(''); // Middle block is empty expect(blocks[2].content).toBe('Block3'); }); it('should handle UTF-8 content with special characters', () => { const content = '中文內容\n\n---\n<!--- block start --->\n🚀 Emoji block\n\n---\nFinal block'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(3); expect(blocks[0].content).toBe('中文內容'); expect(blocks[1].content).toBe('🚀 Emoji block'); expect(blocks[2].content).toBe('Final block'); }); it('should handle very large content efficiently', () => { // Generate large content with multiple blocks const blockSize = 1000; const numBlocks = 50; let largeContent = ''; for (let i = 0; i < numBlocks; i++) { if (i > 0) largeContent += '\n\n---\n<!--- block start --->\n'; largeContent += `Block ${i + 1} `.repeat(blockSize); } const startTime = Date.now(); const blocks = BlockParser.parseBlocks(largeContent); const endTime = Date.now(); expect(blocks).toHaveLength(numBlocks); expect(endTime - startTime).toBeLessThan(100); // Should parse quickly }); it('should handle malformed splitters gracefully', () => { const content = 'Block1\n\n--\nNot a splitter\n\n---\nBlock2'; const blocks = BlockParser.parseBlocks(content); expect(blocks).toHaveLength(2); // Only valid splitter should be recognized expect(blocks[0].content).toBe('Block1\n\n--\nNot a splitter'); expect(blocks[1].content).toBe('Block2'); }); }); describe('Reconstruction Consistency', () => { it('should maintain content consistency through parse->reconstruct cycle', () => { const originalContent = 'Block1\n\n---\n<!--- block start --->\nBlock2\n\n---\nBlock3'; const blocks = BlockParser.parseBlocks(originalContent); // Get all blocks (should reconstruct to original) const reconstructed = BlockParser.getBlockRange(originalContent, blocks.length, false); expect(reconstructed).toBe(originalContent); }); it('should preserve splitter format consistency in partial reconstructions', () => { // 測試案例1:前兩個 block 用舊格式分隔 const mixedContent1 = 'B1\n\n---\nB2\n\n---\n<!--- block start --->\nB3\n\n---\nB4'; const partial1 = BlockParser.getBlockRange(mixedContent1, 2, false); expect(partial1).toContain('B1'); expect(partial1).toContain('B2'); expect(partial1).not.toContain('<!--- block start --->'); // 應該使用舊格式 // 測試案例2:最後兩個 block 其中包含新格式分隔 const mixedContent2 = 'B1\n\n---\nB2\n\n---\n<!--- block start --->\nB3\n\n---\n<!--- block start --->\nB4'; const partial2 = BlockParser.getBlockRange(mixedContent2, 2, true); expect(partial2).toContain('B3'); expect(partial2).toContain('B4'); expect(partial2).toContain('<!--- block start --->'); // 應該保留新格式 }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/pc035860/scratchpad-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server