Skip to main content
Glama
parser.test.ts14.5 kB
/** * Tests for Gutenberg Block Parser * * @package WP_Navigator_MCP */ import { describe, it, expect } from 'vitest'; import { parseGutenbergBlocks, serializeBlocks, isReusableBlock, getReusableBlockRef, flattenBlocks, countBlocks, getBlockTypes, } from './parser.js'; describe('parseGutenbergBlocks', () => { describe('basic blocks', () => { it('should parse a simple paragraph block', () => { const content = `<!-- wp:paragraph --> <p>Hello World</p> <!-- /wp:paragraph -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/paragraph'); expect(blocks[0].attrs).toEqual({}); expect(blocks[0].innerBlocks).toHaveLength(0); expect(blocks[0].innerHTML).toContain('Hello World'); }); it('should parse a heading block with attributes', () => { const content = `<!-- wp:heading {"level":2,"textAlign":"center"} --> <h2 class="has-text-align-center">My Heading</h2> <!-- /wp:heading -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/heading'); expect(blocks[0].attrs).toEqual({ level: 2, textAlign: 'center' }); }); it('should parse a self-closing block', () => { const content = `<!-- wp:separator {"className":"is-style-wide"} /-->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/separator'); expect(blocks[0].attrs).toEqual({ className: 'is-style-wide' }); expect(blocks[0].innerHTML).toBe(''); expect(blocks[0].innerBlocks).toHaveLength(0); }); it('should parse a spacer block (self-closing)', () => { const content = `<!-- wp:spacer {"height":"50px"} /-->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/spacer'); expect(blocks[0].attrs).toEqual({ height: '50px' }); }); }); describe('namespaced blocks', () => { it('should parse ACF block', () => { const content = `<!-- wp:acf/testimonial {"id":"block_123","data":{"author":"John"}} --> <div class="testimonial">Great product!</div> <!-- /wp:acf/testimonial -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('acf/testimonial'); expect(blocks[0].attrs.id).toBe('block_123'); expect(blocks[0].attrs.data).toEqual({ author: 'John' }); }); it('should parse WooCommerce block', () => { const content = `<!-- wp:woocommerce/product-grid {"columns":3,"rows":2} /-->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('woocommerce/product-grid'); expect(blocks[0].attrs).toEqual({ columns: 3, rows: 2 }); }); }); describe('nested blocks (innerBlocks)', () => { it('should parse columns with nested column blocks', () => { const content = `<!-- wp:columns --> <div class="wp-block-columns"> <!-- wp:column {"width":"66.66%"} --> <div class="wp-block-column" style="flex-basis:66.66%"> <!-- wp:paragraph --> <p>Left column content</p> <!-- /wp:paragraph --> </div> <!-- /wp:column --> <!-- wp:column {"width":"33.33%"} --> <div class="wp-block-column" style="flex-basis:33.33%"> <!-- wp:paragraph --> <p>Right column content</p> <!-- /wp:paragraph --> </div> <!-- /wp:column --> </div> <!-- /wp:columns -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/columns'); expect(blocks[0].innerBlocks).toHaveLength(2); // First column expect(blocks[0].innerBlocks[0].blockName).toBe('core/column'); expect(blocks[0].innerBlocks[0].attrs.width).toBe('66.66%'); expect(blocks[0].innerBlocks[0].innerBlocks).toHaveLength(1); expect(blocks[0].innerBlocks[0].innerBlocks[0].blockName).toBe('core/paragraph'); // Second column expect(blocks[0].innerBlocks[1].blockName).toBe('core/column'); expect(blocks[0].innerBlocks[1].attrs.width).toBe('33.33%'); }); it('should parse group block with nested content', () => { const content = `<!-- wp:group {"backgroundColor":"pale-cyan-blue"} --> <div class="wp-block-group has-pale-cyan-blue-background-color has-background"> <!-- wp:heading --> <h2>Group Title</h2> <!-- /wp:heading --> <!-- wp:paragraph --> <p>Group content</p> <!-- /wp:paragraph --> </div> <!-- /wp:group -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/group'); expect(blocks[0].innerBlocks).toHaveLength(2); expect(blocks[0].innerBlocks[0].blockName).toBe('core/heading'); expect(blocks[0].innerBlocks[1].blockName).toBe('core/paragraph'); }); it('should handle deeply nested blocks', () => { const content = `<!-- wp:group --> <div class="wp-block-group"> <!-- wp:columns --> <div class="wp-block-columns"> <!-- wp:column --> <div class="wp-block-column"> <!-- wp:group --> <div class="wp-block-group"> <!-- wp:paragraph --> <p>Deep content</p> <!-- /wp:paragraph --> </div> <!-- /wp:group --> </div> <!-- /wp:column --> </div> <!-- /wp:columns --> </div> <!-- /wp:group -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/group'); const columns = blocks[0].innerBlocks[0]; expect(columns.blockName).toBe('core/columns'); const column = columns.innerBlocks[0]; expect(column.blockName).toBe('core/column'); const innerGroup = column.innerBlocks[0]; expect(innerGroup.blockName).toBe('core/group'); const paragraph = innerGroup.innerBlocks[0]; expect(paragraph.blockName).toBe('core/paragraph'); }); }); describe('reusable blocks', () => { it('should parse reusable block reference', () => { const content = `<!-- wp:block {"ref":1234} /-->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/block'); expect(blocks[0].attrs.ref).toBe(1234); expect(isReusableBlock(blocks[0])).toBe(true); expect(getReusableBlockRef(blocks[0])).toBe(1234); }); it('should identify non-reusable blocks correctly', () => { const content = `<!-- wp:paragraph --> <p>Regular paragraph</p> <!-- /wp:paragraph -->`; const blocks = parseGutenbergBlocks(content); expect(isReusableBlock(blocks[0])).toBe(false); expect(getReusableBlockRef(blocks[0])).toBeNull(); }); }); describe('multiple blocks', () => { it('should parse multiple top-level blocks', () => { const content = `<!-- wp:heading {"level":1} --> <h1>Title</h1> <!-- /wp:heading --> <!-- wp:paragraph --> <p>First paragraph</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>Second paragraph</p> <!-- /wp:paragraph --> <!-- wp:image {"id":123} --> <figure class="wp-block-image"><img src="image.jpg" alt=""/></figure> <!-- /wp:image -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(4); expect(blocks[0].blockName).toBe('core/heading'); expect(blocks[1].blockName).toBe('core/paragraph'); expect(blocks[2].blockName).toBe('core/paragraph'); expect(blocks[3].blockName).toBe('core/image'); }); }); describe('edge cases', () => { it('should handle empty content', () => { expect(parseGutenbergBlocks('')).toEqual([]); }); it('should handle null/undefined content', () => { expect(parseGutenbergBlocks(null as any)).toEqual([]); expect(parseGutenbergBlocks(undefined as any)).toEqual([]); }); it('should handle content with no blocks', () => { const content = '<p>Plain HTML without Gutenberg blocks</p>'; expect(parseGutenbergBlocks(content)).toEqual([]); }); it('should handle malformed JSON in attributes', () => { const content = `<!-- wp:paragraph {"invalid":} --> <p>Test</p> <!-- /wp:paragraph -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].attrs).toEqual({}); }); it('should handle block with no attributes', () => { const content = `<!-- wp:paragraph --> <p>No attrs</p> <!-- /wp:paragraph -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].attrs).toEqual({}); }); }); describe('complex blocks', () => { it('should parse image block with all attributes', () => { const content = `<!-- wp:image {"id":123,"sizeSlug":"large","linkDestination":"media","align":"center","className":"custom-class"} --> <figure class="wp-block-image aligncenter size-large custom-class"> <a href="https://example.com/image.jpg"> <img src="https://example.com/image-1024x768.jpg" alt="Alt text" class="wp-image-123"/> </a> <figcaption>Image caption</figcaption> </figure> <!-- /wp:image -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/image'); expect(blocks[0].attrs).toEqual({ id: 123, sizeSlug: 'large', linkDestination: 'media', align: 'center', className: 'custom-class', }); }); it('should parse buttons block with inner button blocks', () => { const content = `<!-- wp:buttons --> <div class="wp-block-buttons"> <!-- wp:button {"backgroundColor":"primary"} --> <div class="wp-block-button"><a class="wp-block-button__link has-primary-background-color has-background">Click Me</a></div> <!-- /wp:button --> <!-- wp:button {"backgroundColor":"secondary"} --> <div class="wp-block-button"><a class="wp-block-button__link has-secondary-background-color has-background">Another</a></div> <!-- /wp:button --> </div> <!-- /wp:buttons -->`; const blocks = parseGutenbergBlocks(content); expect(blocks).toHaveLength(1); expect(blocks[0].blockName).toBe('core/buttons'); expect(blocks[0].innerBlocks).toHaveLength(2); expect(blocks[0].innerBlocks[0].blockName).toBe('core/button'); expect(blocks[0].innerBlocks[0].attrs.backgroundColor).toBe('primary'); }); }); }); describe('serializeBlocks', () => { it('should serialize a simple paragraph', () => { const blocks = [ { blockName: 'core/paragraph', attrs: {}, innerBlocks: [], innerHTML: '<p>Hello World</p>', innerContent: ['<p>Hello World</p>'], }, ]; const result = serializeBlocks(blocks); expect(result).toContain('<!-- wp:paragraph -->'); expect(result).toContain('<p>Hello World</p>'); expect(result).toContain('<!-- /wp:paragraph -->'); }); it('should serialize block with attributes', () => { const blocks = [ { blockName: 'core/heading', attrs: { level: 2 }, innerBlocks: [], innerHTML: '<h2>Title</h2>', innerContent: ['<h2>Title</h2>'], }, ]; const result = serializeBlocks(blocks); expect(result).toContain('<!-- wp:heading {"level":2} -->'); }); it('should serialize self-closing block', () => { const blocks = [ { blockName: 'core/separator', attrs: { className: 'is-style-wide' }, innerBlocks: [], innerHTML: '', innerContent: [], }, ]; const result = serializeBlocks(blocks); expect(result).toBe('<!-- wp:separator {"className":"is-style-wide"} /-->'); }); }); describe('utility functions', () => { describe('flattenBlocks', () => { it('should flatten nested blocks', () => { const blocks = [ { blockName: 'core/group', attrs: {}, innerBlocks: [ { blockName: 'core/paragraph', attrs: {}, innerBlocks: [], innerHTML: '<p>Test</p>', innerContent: ['<p>Test</p>'], }, ], innerHTML: '', innerContent: [], }, ]; const flat = flattenBlocks(blocks); expect(flat).toHaveLength(2); expect(flat[0].blockName).toBe('core/group'); expect(flat[1].blockName).toBe('core/paragraph'); }); }); describe('countBlocks', () => { it('should count all blocks including nested', () => { const blocks = [ { blockName: 'core/columns', attrs: {}, innerBlocks: [ { blockName: 'core/column', attrs: {}, innerBlocks: [ { blockName: 'core/paragraph', attrs: {}, innerBlocks: [], innerHTML: '', innerContent: [], }, ], innerHTML: '', innerContent: [], }, { blockName: 'core/column', attrs: {}, innerBlocks: [ { blockName: 'core/paragraph', attrs: {}, innerBlocks: [], innerHTML: '', innerContent: [], }, ], innerHTML: '', innerContent: [], }, ], innerHTML: '', innerContent: [], }, ]; expect(countBlocks(blocks)).toBe(5); // columns + 2 columns + 2 paragraphs }); }); describe('getBlockTypes', () => { it('should return unique block types', () => { const blocks = [ { blockName: 'core/paragraph', attrs: {}, innerBlocks: [], innerHTML: '', innerContent: [], }, { blockName: 'core/heading', attrs: {}, innerBlocks: [], innerHTML: '', innerContent: [] }, { blockName: 'core/paragraph', attrs: {}, innerBlocks: [], innerHTML: '', innerContent: [], }, { blockName: 'core/image', attrs: {}, innerBlocks: [], innerHTML: '', innerContent: [] }, ]; const types = getBlockTypes(blocks); expect(types).toEqual(['core/heading', 'core/image', 'core/paragraph']); }); }); });

Latest Blog Posts

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/littlebearapps/wp-navigator-mcp'

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