Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions __tests__/lib/mdxish/mdxish-jsx-to-mdast.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Callout, EmbedBlock, ImageBlock, Recipe } from '../../../types';
import type { Root } from 'mdast';
import type { Anchor, Callout, EmbedBlock, ImageBlock, Recipe } from '../../../types';
import type { Paragraph, Root } from 'mdast';

import { NodeTypes } from '../../../enums';
import { mdxishAstProcessor } from '../../../lib/mdxish';
Expand Down Expand Up @@ -104,6 +104,66 @@ This is a warning message.
});
});

describe('Anchor component', () => {
it('should transform <Anchor> to readme-anchor node', () => {
const md = 'Start by <Anchor href="https://readme.com">ReadMe</Anchor> today.';
const ast = processWithNewTypes(md);

expect(ast.children).toHaveLength(1);
expect(ast.children[0].type).toBe('paragraph');

const para = ast.children[0] as Paragraph;
const anchorNode = para.children.find(c => c.type === NodeTypes.anchor) as Anchor;
expect(anchorNode).toBeDefined();
expect(anchorNode.data?.hProperties?.href).toBe('https://readme.com');
expect(anchorNode.children[0]).toMatchObject({ type: 'text', value: 'ReadMe' });
});

it('should preserve target="_blank" on the Anchor node', () => {
const md = '<Anchor label="**Guides**" target="_blank" href="https://docs.readme.com">Guides</Anchor>';
const ast = processWithNewTypes(md);

const para = ast.children[0] as Paragraph;
const anchorNode = para.children.find(c => c.type === NodeTypes.anchor) as Anchor;
expect(anchorNode).toBeDefined();
expect(anchorNode.data?.hProperties?.href).toBe('https://docs.readme.com');
expect(anchorNode.data?.hProperties?.target).toBe('_blank');
expect(anchorNode.children[0]).toMatchObject({ type: 'text', value: 'Guides' });
});

it('should omit target when not provided', () => {
const md = '<Anchor href="https://readme.com">ReadMe</Anchor>';
const ast = processWithNewTypes(md);

const para = ast.children[0] as Paragraph;
const anchorNode = para.children.find(c => c.type === NodeTypes.anchor) as Anchor;
expect(anchorNode).toBeDefined();
expect(anchorNode.data?.hProperties?.href).toBe('https://readme.com');
expect(anchorNode.data?.hProperties?.target).toBeUndefined();
});

it('should preserve title attribute', () => {
const md = '<Anchor href="https://readme.com" title="Home">ReadMe</Anchor>';
const ast = processWithNewTypes(md);

const para = ast.children[0] as Paragraph;
const anchorNode = para.children.find(c => c.type === NodeTypes.anchor) as Anchor;
expect(anchorNode.data?.hProperties?.title).toBe('Home');
});

it('should handle empty Anchor children', () => {
const md = '<Anchor href="https://readme.com"></Anchor>';
const ast = processWithNewTypes(md);

expect(ast.children).toHaveLength(1);
const para = ast.children[0] as Paragraph;
const anchorNode = para.children.find(c => c.type === NodeTypes.anchor) as Anchor;
expect(anchorNode).toBeDefined();
expect(anchorNode.data?.hProperties?.href).toBe('https://readme.com');
expect(anchorNode.children).toHaveLength(0);
});
});

describe('Recipe component', () => {
it('should transform <Recipe /> to recipe node', () => {
const md = '<Recipe slug="recipe-1" title="Recipe 1" />';
Expand Down Expand Up @@ -331,5 +391,18 @@ Some callout content
expect(ast.children).toHaveLength(1);
expect(ast.children[0].type).toBe('mdxJsxFlowElement');
});

it('should leave Anchor as raw html nodes (rendering path unaffected)', () => {
const md = '<Anchor href="https://readme.com">ReadMe</Anchor>';
const ast = processWithoutNewTypes(md);

// Anchor is excluded from mdxishComponentBlocks so it stays as raw html nodes
// inside the paragraph — no mdxJsxFlowElement is created
expect(ast.children[0].type).toBe('paragraph');
const para = ast.children[0] as Paragraph;
const htmlNodes = para.children.filter(c => c.type === 'html');
expect(htmlNodes.length).toBeGreaterThan(0);
expect(para.children.find(c => c.type === 'mdxJsxFlowElement')).toBeUndefined();
});
});
});
35 changes: 35 additions & 0 deletions __tests__/transformers/mdxish-component-blocks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,4 +337,39 @@ hello
});
});
});

describe('Anchor component (inline, excluded)', () => {
it('should NOT convert <Anchor> to mdxJsxFlowElement', () => {
// Anchor is an inline component and must remain as raw html nodes so that
// the rendering path (rehypeRaw) can process it correctly inline in paragraphs.
const markdown = '<Anchor href="https://readme.com">ReadMe</Anchor>';
const tree = parseWithPlugin(markdown);

expect(findNodesByType(tree, 'mdxJsxFlowElement')).toHaveLength(0);
});

it('should leave <Anchor> as raw html nodes inside a paragraph', () => {
const markdown = 'Start by <Anchor href="https://readme.com" target="_blank">ReadMe</Anchor> today.';
const tree = parseWithPlugin(markdown);

expect(findNodesByType(tree, 'mdxJsxFlowElement')).toHaveLength(0);
// The opening and closing tags stay as html nodes inside the paragraph
const paragraphs = findNodesByType(tree, 'paragraph');
expect(paragraphs).toHaveLength(1);
const htmlNodes = paragraphs[0].children.filter((c: { type: string }) => c.type === 'html');
expect(htmlNodes.length).toBeGreaterThanOrEqual(2);
});

it('should still process other PascalCase components in the same document', () => {
// Ensuring Anchor exclusion does not affect sibling components
const markdown = `<Image src="test.png" alt="Test" />
Some text with <Anchor href="https://readme.com">link</Anchor> inline.`;
const tree = parseWithPlugin(markdown);

const mdxNodes = findNodesByType(tree, 'mdxJsxFlowElement');
expect(mdxNodes).toHaveLength(1);
expect((mdxNodes[0] as { name?: string }).name).toBe('Image');
});
});
});
Loading
Loading