From 5f37cc22a0c505ba301605b637422c92c1a66751 Mon Sep 17 00:00:00 2001 From: Maximilian Falco Widjaya Date: Mon, 23 Feb 2026 15:47:07 +1100 Subject: [PATCH 01/12] chore(tests): add some renderMdxish tests for readme custom components --- .../lib/render-mdxish/Accordion.test.tsx | 55 +++++++++ __tests__/lib/render-mdxish/Cards.test.tsx | 109 ++++++++++++++++++ __tests__/lib/render-mdxish/Columns.test.tsx | 75 ++++++++++++ __tests__/lib/render-mdxish/Embed.test.tsx | 72 ++++++++++++ __tests__/lib/render-mdxish/Heading.test.tsx | 45 ++++++++ __tests__/lib/render-mdxish/MCPIntro.test.tsx | 28 +++++ .../render-mdxish/PostmanRunButton.test.tsx | 40 +++++++ __tests__/lib/render-mdxish/Recipe.test.tsx | 22 ++++ __tests__/lib/render-mdxish/Table.test.tsx | 59 ++++++++++ .../lib/render-mdxish/TailwindRoot.test.tsx | 39 +++++++ 10 files changed, 544 insertions(+) create mode 100644 __tests__/lib/render-mdxish/Accordion.test.tsx create mode 100644 __tests__/lib/render-mdxish/Cards.test.tsx create mode 100644 __tests__/lib/render-mdxish/Columns.test.tsx create mode 100644 __tests__/lib/render-mdxish/Embed.test.tsx create mode 100644 __tests__/lib/render-mdxish/Heading.test.tsx create mode 100644 __tests__/lib/render-mdxish/MCPIntro.test.tsx create mode 100644 __tests__/lib/render-mdxish/PostmanRunButton.test.tsx create mode 100644 __tests__/lib/render-mdxish/Recipe.test.tsx create mode 100644 __tests__/lib/render-mdxish/Table.test.tsx create mode 100644 __tests__/lib/render-mdxish/TailwindRoot.test.tsx diff --git a/__tests__/lib/render-mdxish/Accordion.test.tsx b/__tests__/lib/render-mdxish/Accordion.test.tsx new file mode 100644 index 000000000..8b121947f --- /dev/null +++ b/__tests__/lib/render-mdxish/Accordion.test.tsx @@ -0,0 +1,55 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Accordion renderer', () => { + describe('given a basic Accordion', () => { + const md = ` +Content +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a details element with Accordion class', () => { + const { container } = render(); + expect(container.querySelector('details.Accordion')).toBeInTheDocument(); + }); + + it('should render a summary with the title', () => { + const { container } = render(); + const summary = container.querySelector('summary.Accordion-title'); + expect(summary).toBeInTheDocument(); + expect(summary).toHaveTextContent('Title'); + }); + + it('should render children in Accordion-content', () => { + const { container } = render(); + const content = container.querySelector('.Accordion-content'); + expect(content).toBeInTheDocument(); + expect(content).toHaveTextContent('Content'); + }); + }); + + describe('given an Accordion with icon props', () => { + const md = ` +Settings content +`; + const mod = renderMdxish(mdxish(md)); + + it('should render an icon element', () => { + const { container } = render(); + expect(container.querySelector('i.Accordion-icon')).toBeInTheDocument(); + }); + + it('should apply the icon color style', () => { + const { container } = render(); + const icon = container.querySelector('i.Accordion-icon'); + expect(icon).toHaveStyle({ color: '#FF0000' }); + }); + }); +}); diff --git a/__tests__/lib/render-mdxish/Cards.test.tsx b/__tests__/lib/render-mdxish/Cards.test.tsx new file mode 100644 index 000000000..b6ef1a0af --- /dev/null +++ b/__tests__/lib/render-mdxish/Cards.test.tsx @@ -0,0 +1,109 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Cards renderer', () => { + describe('given a Cards with Card children', () => { + const md = ` + + First content + Second content + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a CardsGrid wrapper', () => { + const { container } = render(); + expect(container.querySelector('.CardsGrid')).toBeInTheDocument(); + }); + + it('should render Card children', () => { + const { container } = render(); + const cards = container.querySelectorAll('.Card'); + expect(cards).toHaveLength(2); + }); + + it('should render card titles', () => { + const { container } = render(); + const titles = container.querySelectorAll('.Card-title'); + expect(titles).toHaveLength(2); + expect(titles[0]).toHaveTextContent('First'); + expect(titles[1]).toHaveTextContent('Second'); + }); + }); + + describe('given a Card with href', () => { + const md = ` + + Linked + +`; + const mod = renderMdxish(mdxish(md)); + + it('should render as an anchor element', () => { + const { container } = render(); + const card = container.querySelector('a.Card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveAttribute('href', 'https://example.com'); + }); + + it('should render the arrow icon', () => { + const { container } = render(); + expect(container.querySelector('.Card-arrow')).toBeInTheDocument(); + }); + }); + + describe('given a Card without href', () => { + const md = ` + + Static + +`; + const mod = renderMdxish(mdxish(md)); + + it('should render as a div element', () => { + const { container } = render(); + const card = container.querySelector('div.Card'); + expect(card).toBeInTheDocument(); + }); + }); + + describe('given a Card with icon and badge props', () => { + const md = ` + + Featured content + +`; + const mod = renderMdxish(mdxish(md)); + + it('should render an icon element', () => { + const { container } = render(); + expect(container.querySelector('.Card-icon')).toBeInTheDocument(); + }); + + it('should render a badge element', () => { + const { container } = render(); + const badge = container.querySelector('.Card-badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveTextContent('New'); + }); + + it('should pass props through the HAST tree', () => { + const tree = mdxish(md); + const cardsNode = tree.children[0] as Element; + const cardChild = cardsNode.children.find( + (child): child is Element => child.type === 'element' && child.tagName === 'Card', + ); + expect(cardChild?.properties?.icon).toBe('fa-star'); + expect(cardChild?.properties?.badge).toBe('New'); + }); + }); +}); diff --git a/__tests__/lib/render-mdxish/Columns.test.tsx b/__tests__/lib/render-mdxish/Columns.test.tsx new file mode 100644 index 000000000..8d425d68d --- /dev/null +++ b/__tests__/lib/render-mdxish/Columns.test.tsx @@ -0,0 +1,75 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Columns renderer', () => { + describe('given Columns with two Column children', () => { + const md = ` + + Col 1 + Col 2 + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a Columns wrapper with grid style', () => { + const { container } = render(); + const columns = container.querySelector('.Columns'); + expect(columns).toBeInTheDocument(); + expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(2, auto)' }); + }); + + it('should render Column children', () => { + const { container } = render(); + const cols = container.querySelectorAll('.Column'); + expect(cols).toHaveLength(2); + expect(cols[0]).toHaveTextContent('Col 1'); + expect(cols[1]).toHaveTextContent('Col 2'); + }); + }); + + describe('given Columns with three children', () => { + const md = ` + + A + B + C + +`; + const mod = renderMdxish(mdxish(md)); + + it('should set grid columns based on child count', () => { + const { container } = render(); + const columns = container.querySelector('.Columns'); + expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(3, auto)' }); + }); + }); + + describe('given Columns with the HAST tree', () => { + const md = ` + + Col 1 + Col 2 + +`; + + it('should produce Column children in the tree', () => { + const tree = mdxish(md); + const columnsNode = tree.children[0] as Element; + expect(columnsNode.tagName).toBe('Columns'); + + const columnChildren = columnsNode.children.filter( + (child): child is Element => child.type === 'element' && child.tagName === 'Column', + ); + expect(columnChildren).toHaveLength(2); + }); + }); +}); diff --git a/__tests__/lib/render-mdxish/Embed.test.tsx b/__tests__/lib/render-mdxish/Embed.test.tsx new file mode 100644 index 000000000..3563930ed --- /dev/null +++ b/__tests__/lib/render-mdxish/Embed.test.tsx @@ -0,0 +1,72 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Embed renderer', () => { + describe('given an Embed in link mode', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render an embed wrapper', () => { + const { container } = render(); + expect(container.querySelector('.embed')).toBeInTheDocument(); + }); + + it('should render an embed-link anchor', () => { + const { container } = render(); + const link = container.querySelector('a.embed-link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', 'https://example.com'); + }); + + it('should render the embed title', () => { + const { container } = render(); + const title = container.querySelector('.embed-title'); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Example'); + }); + }); + + describe('given an Embed in iframe mode', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render an iframe element', () => { + const { container } = render(); + const iframe = container.querySelector('iframe'); + expect(iframe).toBeInTheDocument(); + expect(iframe).toHaveAttribute('src', 'https://example.com'); + expect(iframe).toHaveAttribute('title', 'Example'); + }); + }); + + describe('given the HAST tree', () => { + const md = ` + +`; + + it('should pass props through the HAST tree', () => { + const tree = mdxish(md); + const node = tree.children[0] as Element; + expect(node.tagName).toBe('embed'); + expect(node.properties?.url).toBe('https://example.com'); + expect(node.properties?.title).toBe('Example'); + }); + }); +}); diff --git a/__tests__/lib/render-mdxish/Heading.test.tsx b/__tests__/lib/render-mdxish/Heading.test.tsx new file mode 100644 index 000000000..9ca9297d7 --- /dev/null +++ b/__tests__/lib/render-mdxish/Heading.test.tsx @@ -0,0 +1,45 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Heading renderer', () => { + describe.each([ + ['# H1', 'h1', 1], + ['## H2', 'h2', 2], + ['### H3', 'h3', 3], + ['#### H4', 'h4', 4], + ['##### H5', 'h5', 5], + ['###### H6', 'h6', 6], + ] as const)('given markdown "%s"', (md, tag, depth) => { + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it(`should render an ${tag} element`, () => { + const { container } = render(); + expect(container.querySelector(tag)).toBeInTheDocument(); + }); + + it('should have the heading class', () => { + const { container } = render(); + expect(container.querySelector(`.heading-${depth}`)).toBeInTheDocument(); + }); + + it('should render the heading text', () => { + const { container } = render(); + const heading = container.querySelector('.heading-text'); + expect(heading).toHaveTextContent(`H${depth}`); + }); + + it('should render an anchor link', () => { + const { container } = render(); + const anchor = container.querySelector('a.heading-anchor-icon'); + expect(anchor).toBeInTheDocument(); + expect(anchor?.getAttribute('href')).toMatch(/^#/); + }); + }); +}); diff --git a/__tests__/lib/render-mdxish/MCPIntro.test.tsx b/__tests__/lib/render-mdxish/MCPIntro.test.tsx new file mode 100644 index 000000000..84b56f44f --- /dev/null +++ b/__tests__/lib/render-mdxish/MCPIntro.test.tsx @@ -0,0 +1,28 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('MCPIntro renderer', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a MCPIntro container', () => { + const { container } = render(); + expect(container.querySelector('.MCPIntro')).toBeInTheDocument(); + }); + + it('should render skeleton placeholder divs', () => { + const { container } = render(); + const mcpIntro = container.querySelector('.MCPIntro'); + const divs = mcpIntro?.querySelectorAll('div'); + expect(divs!.length).toBeGreaterThan(1); + }); +}); diff --git a/__tests__/lib/render-mdxish/PostmanRunButton.test.tsx b/__tests__/lib/render-mdxish/PostmanRunButton.test.tsx new file mode 100644 index 000000000..0f1e0d184 --- /dev/null +++ b/__tests__/lib/render-mdxish/PostmanRunButton.test.tsx @@ -0,0 +1,40 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('PostmanRunButton renderer', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a postman-run-button element', () => { + const { container } = render(); + expect(container.querySelector('.postman-run-button')).toBeInTheDocument(); + }); + + it('should set data attributes for simple props', () => { + const { container } = render(); + const button = container.querySelector('.postman-run-button'); + expect(button).toHaveAttribute('data-postman-action', 'collection/fork'); + expect(button).toHaveAttribute('data-postman-visibility', 'public'); + }); + + it('should pass props through the HAST tree', () => { + const tree = mdxish(md); + const node = tree.children[0] as Element; + expect(node.tagName).toBe('PostmanRunButton'); + expect(node.properties?.action).toBe('collection/fork'); + expect(node.properties?.colLectionId).toBe('123'); + expect(node.properties?.colLectionUrl).toBe('https://example.com'); + expect(node.properties?.visibility).toBe('public'); + }); +}); diff --git a/__tests__/lib/render-mdxish/Recipe.test.tsx b/__tests__/lib/render-mdxish/Recipe.test.tsx new file mode 100644 index 000000000..89ac5a00d --- /dev/null +++ b/__tests__/lib/render-mdxish/Recipe.test.tsx @@ -0,0 +1,22 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Recipe renderer', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render skeleton placeholder divs', () => { + const { container } = render(); + const divs = container.querySelectorAll('div'); + expect(divs.length).toBeGreaterThan(1); + }); +}); diff --git a/__tests__/lib/render-mdxish/Table.test.tsx b/__tests__/lib/render-mdxish/Table.test.tsx new file mode 100644 index 000000000..7390b2c7f --- /dev/null +++ b/__tests__/lib/render-mdxish/Table.test.tsx @@ -0,0 +1,59 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../../lib'; + +describe('Table renderer', () => { + describe('given a markdown table', () => { + const md = `| col1 | col2 | +|---|---| +| a | b |`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a rdmd-table wrapper', () => { + const { container } = render(); + expect(container.querySelector('.rdmd-table')).toBeInTheDocument(); + }); + + it('should render a table element', () => { + const { container } = render(); + expect(container.querySelector('table')).toBeInTheDocument(); + }); + + it('should render header cells', () => { + const { container } = render(); + const headers = container.querySelectorAll('th'); + expect(headers).toHaveLength(2); + expect(headers[0]).toHaveTextContent('col1'); + expect(headers[1]).toHaveTextContent('col2'); + }); + + it('should render body cells', () => { + const { container } = render(); + const cells = container.querySelectorAll('td'); + expect(cells).toHaveLength(2); + expect(cells[0]).toHaveTextContent('a'); + expect(cells[1]).toHaveTextContent('b'); + }); + }); + + describe('given a multi-row table', () => { + const md = `| name | value | +|---|---| +| foo | 1 | +| bar | 2 | +| baz | 3 |`; + const mod = renderMdxish(mdxish(md)); + + it('should render all rows', () => { + const { container } = render(); + const rows = container.querySelectorAll('tbody tr'); + expect(rows).toHaveLength(3); + }); + }); +}); diff --git a/__tests__/lib/render-mdxish/TailwindRoot.test.tsx b/__tests__/lib/render-mdxish/TailwindRoot.test.tsx new file mode 100644 index 000000000..b616012a5 --- /dev/null +++ b/__tests__/lib/render-mdxish/TailwindRoot.test.tsx @@ -0,0 +1,39 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import TailwindRoot from '../../../components/TailwindRoot'; + +describe('TailwindRoot renderer', () => { + describe('given flow=true (block-level)', () => { + it('should not error when rendering', () => { + expect(() => render(Content)).not.toThrow(); + }); + + it('should render a div element', () => { + const { container } = render(Content); + const root = container.querySelector('div.readme-tailwind'); + expect(root).toBeInTheDocument(); + }); + + it('should render children', () => { + const { container } = render(Content); + const root = container.querySelector('.readme-tailwind'); + expect(root).toHaveTextContent('Content'); + }); + }); + + describe('given flow=false (inline)', () => { + it('should render a span element', () => { + const { container } = render(Inline); + const root = container.querySelector('span.readme-tailwind'); + expect(root).toBeInTheDocument(); + }); + + it('should render children', () => { + const { container } = render(Inline); + const root = container.querySelector('.readme-tailwind'); + expect(root).toHaveTextContent('Inline'); + }); + }); +}); From 0488361dda149f7b0fd962e00845dda12512b038 Mon Sep 17 00:00:00 2001 From: Maximilian Falco Widjaya Date: Mon, 23 Feb 2026 16:08:45 +1100 Subject: [PATCH 02/12] chore: move component tests files into a centralized components test dir --- __tests__/components/Accordion.test.tsx | 61 ++++++++++ __tests__/components/Cards.test.tsx | 115 ++++++++++++++++++ __tests__/components/Columns.test.tsx | 81 ++++++++++++ __tests__/components/Embed.test.tsx | 78 ++++++++++++ __tests__/components/Heading.test.tsx | 51 ++++++++ __tests__/components/MCPIntro.test.tsx | 34 ++++++ .../components/PostmanRunButton.test.tsx | 46 +++++++ __tests__/components/Recipe.test.tsx | 28 +++++ __tests__/components/Table.test.tsx | 65 ++++++++++ __tests__/components/TailwindRoot.test.tsx | 45 +++++++ .../lib/render-mdxish/Accordion.test.tsx | 55 --------- __tests__/lib/render-mdxish/Cards.test.tsx | 109 ----------------- __tests__/lib/render-mdxish/Columns.test.tsx | 75 ------------ __tests__/lib/render-mdxish/Embed.test.tsx | 72 ----------- __tests__/lib/render-mdxish/Heading.test.tsx | 45 ------- __tests__/lib/render-mdxish/MCPIntro.test.tsx | 28 ----- .../render-mdxish/PostmanRunButton.test.tsx | 40 ------ __tests__/lib/render-mdxish/Recipe.test.tsx | 22 ---- __tests__/lib/render-mdxish/Table.test.tsx | 59 --------- .../lib/render-mdxish/TailwindRoot.test.tsx | 39 ------ 20 files changed, 604 insertions(+), 544 deletions(-) create mode 100644 __tests__/components/Accordion.test.tsx create mode 100644 __tests__/components/Cards.test.tsx create mode 100644 __tests__/components/Columns.test.tsx create mode 100644 __tests__/components/Embed.test.tsx create mode 100644 __tests__/components/Heading.test.tsx create mode 100644 __tests__/components/MCPIntro.test.tsx create mode 100644 __tests__/components/PostmanRunButton.test.tsx create mode 100644 __tests__/components/Recipe.test.tsx create mode 100644 __tests__/components/Table.test.tsx create mode 100644 __tests__/components/TailwindRoot.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Accordion.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Cards.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Columns.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Embed.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Heading.test.tsx delete mode 100644 __tests__/lib/render-mdxish/MCPIntro.test.tsx delete mode 100644 __tests__/lib/render-mdxish/PostmanRunButton.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Recipe.test.tsx delete mode 100644 __tests__/lib/render-mdxish/Table.test.tsx delete mode 100644 __tests__/lib/render-mdxish/TailwindRoot.test.tsx diff --git a/__tests__/components/Accordion.test.tsx b/__tests__/components/Accordion.test.tsx new file mode 100644 index 000000000..f50403132 --- /dev/null +++ b/__tests__/components/Accordion.test.tsx @@ -0,0 +1,61 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Accordion', () => { + describe('mdxish', () => { + describe('given a basic Accordion', () => { + const md = ` +Content +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a details element with Accordion class', () => { + const { container } = render(); + expect(container.querySelector('details.Accordion')).toBeInTheDocument(); + }); + + it('should render a summary with the title', () => { + const { container } = render(); + const summary = container.querySelector('summary.Accordion-title'); + expect(summary).toBeInTheDocument(); + expect(summary).toHaveTextContent('Title'); + }); + + it('should render children in Accordion-content', () => { + const { container } = render(); + const content = container.querySelector('.Accordion-content'); + expect(content).toBeInTheDocument(); + expect(content).toHaveTextContent('Content'); + }); + }); + + describe('given an Accordion with icon props', () => { + const md = ` +Settings content +`; + const mod = renderMdxish(mdxish(md)); + + it('should render an icon element', () => { + const { container } = render(); + expect(container.querySelector('i.Accordion-icon')).toBeInTheDocument(); + }); + + it('should apply the icon color style', () => { + const { container } = render(); + const icon = container.querySelector('i.Accordion-icon'); + expect(icon).toHaveStyle({ color: '#FF0000' }); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/Cards.test.tsx b/__tests__/components/Cards.test.tsx new file mode 100644 index 000000000..ef2757d1e --- /dev/null +++ b/__tests__/components/Cards.test.tsx @@ -0,0 +1,115 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Cards', () => { + describe('mdxish', () => { + describe('given a Cards with Card children', () => { + const md = ` + + First content + Second content + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a CardsGrid wrapper', () => { + const { container } = render(); + expect(container.querySelector('.CardsGrid')).toBeInTheDocument(); + }); + + it('should render Card children', () => { + const { container } = render(); + const cards = container.querySelectorAll('.Card'); + expect(cards).toHaveLength(2); + }); + + it('should render card titles', () => { + const { container } = render(); + const titles = container.querySelectorAll('.Card-title'); + expect(titles).toHaveLength(2); + expect(titles[0]).toHaveTextContent('First'); + expect(titles[1]).toHaveTextContent('Second'); + }); + }); + + describe('given a Card with href', () => { + const md = ` + + Linked + +`; + const mod = renderMdxish(mdxish(md)); + + it('should render as an anchor element', () => { + const { container } = render(); + const card = container.querySelector('a.Card'); + expect(card).toBeInTheDocument(); + expect(card).toHaveAttribute('href', 'https://example.com'); + }); + + it('should render the arrow icon', () => { + const { container } = render(); + expect(container.querySelector('.Card-arrow')).toBeInTheDocument(); + }); + }); + + describe('given a Card without href', () => { + const md = ` + + Static + +`; + const mod = renderMdxish(mdxish(md)); + + it('should render as a div element', () => { + const { container } = render(); + const card = container.querySelector('div.Card'); + expect(card).toBeInTheDocument(); + }); + }); + + describe('given a Card with icon and badge props', () => { + const md = ` + + Featured content + +`; + const mod = renderMdxish(mdxish(md)); + + it('should render an icon element', () => { + const { container } = render(); + expect(container.querySelector('.Card-icon')).toBeInTheDocument(); + }); + + it('should render a badge element', () => { + const { container } = render(); + const badge = container.querySelector('.Card-badge'); + expect(badge).toBeInTheDocument(); + expect(badge).toHaveTextContent('New'); + }); + + it('should pass props through the HAST tree', () => { + const tree = mdxish(md); + const cardsNode = tree.children[0] as Element; + const cardChild = cardsNode.children.find( + (child): child is Element => child.type === 'element' && child.tagName === 'Card', + ); + expect(cardChild?.properties?.icon).toBe('fa-star'); + expect(cardChild?.properties?.badge).toBe('New'); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/Columns.test.tsx b/__tests__/components/Columns.test.tsx new file mode 100644 index 000000000..ff868604f --- /dev/null +++ b/__tests__/components/Columns.test.tsx @@ -0,0 +1,81 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Columns', () => { + describe('mdxish', () => { + describe('given Columns with two Column children', () => { + const md = ` + + Col 1 + Col 2 + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a Columns wrapper with grid style', () => { + const { container } = render(); + const columns = container.querySelector('.Columns'); + expect(columns).toBeInTheDocument(); + expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(2, auto)' }); + }); + + it('should render Column children', () => { + const { container } = render(); + const cols = container.querySelectorAll('.Column'); + expect(cols).toHaveLength(2); + expect(cols[0]).toHaveTextContent('Col 1'); + expect(cols[1]).toHaveTextContent('Col 2'); + }); + }); + + describe('given Columns with three children', () => { + const md = ` + + A + B + C + +`; + const mod = renderMdxish(mdxish(md)); + + it('should set grid columns based on child count', () => { + const { container } = render(); + const columns = container.querySelector('.Columns'); + expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(3, auto)' }); + }); + }); + + describe('given Columns with the HAST tree', () => { + const md = ` + + Col 1 + Col 2 + +`; + + it('should produce Column children in the tree', () => { + const tree = mdxish(md); + const columnsNode = tree.children[0] as Element; + expect(columnsNode.tagName).toBe('Columns'); + + const columnChildren = columnsNode.children.filter( + (child): child is Element => child.type === 'element' && child.tagName === 'Column', + ); + expect(columnChildren).toHaveLength(2); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/Embed.test.tsx b/__tests__/components/Embed.test.tsx new file mode 100644 index 000000000..a7c921e9a --- /dev/null +++ b/__tests__/components/Embed.test.tsx @@ -0,0 +1,78 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Embed', () => { + describe('mdxish', () => { + describe('given an Embed in link mode', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render an embed wrapper', () => { + const { container } = render(); + expect(container.querySelector('.embed')).toBeInTheDocument(); + }); + + it('should render an embed-link anchor', () => { + const { container } = render(); + const link = container.querySelector('a.embed-link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', 'https://example.com'); + }); + + it('should render the embed title', () => { + const { container } = render(); + const title = container.querySelector('.embed-title'); + expect(title).toBeInTheDocument(); + expect(title).toHaveTextContent('Example'); + }); + }); + + describe('given an Embed in iframe mode', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render an iframe element', () => { + const { container } = render(); + const iframe = container.querySelector('iframe'); + expect(iframe).toBeInTheDocument(); + expect(iframe).toHaveAttribute('src', 'https://example.com'); + expect(iframe).toHaveAttribute('title', 'Example'); + }); + }); + + describe('given the HAST tree', () => { + const md = ` + +`; + + it('should pass props through the HAST tree', () => { + const tree = mdxish(md); + const node = tree.children[0] as Element; + expect(node.tagName).toBe('embed'); + expect(node.properties?.url).toBe('https://example.com'); + expect(node.properties?.title).toBe('Example'); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/Heading.test.tsx b/__tests__/components/Heading.test.tsx new file mode 100644 index 000000000..6ccccd862 --- /dev/null +++ b/__tests__/components/Heading.test.tsx @@ -0,0 +1,51 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Heading', () => { + describe('mdxish', () => { + describe.each([ + ['# H1', 'h1', 1], + ['## H2', 'h2', 2], + ['### H3', 'h3', 3], + ['#### H4', 'h4', 4], + ['##### H5', 'h5', 5], + ['###### H6', 'h6', 6], + ] as const)('given markdown "%s"', (md, tag, depth) => { + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it(`should render an ${tag} element`, () => { + const { container } = render(); + expect(container.querySelector(tag)).toBeInTheDocument(); + }); + + it('should have the heading class', () => { + const { container } = render(); + expect(container.querySelector(`.heading-${depth}`)).toBeInTheDocument(); + }); + + it('should render the heading text', () => { + const { container } = render(); + const heading = container.querySelector('.heading-text'); + expect(heading).toHaveTextContent(`H${depth}`); + }); + + it('should render an anchor link', () => { + const { container } = render(); + const anchor = container.querySelector('a.heading-anchor-icon'); + expect(anchor).toBeInTheDocument(); + expect(anchor?.getAttribute('href')).toMatch(/^#/); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/MCPIntro.test.tsx b/__tests__/components/MCPIntro.test.tsx new file mode 100644 index 000000000..dc639af33 --- /dev/null +++ b/__tests__/components/MCPIntro.test.tsx @@ -0,0 +1,34 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('MCPIntro', () => { + describe('mdxish', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a MCPIntro container', () => { + const { container } = render(); + expect(container.querySelector('.MCPIntro')).toBeInTheDocument(); + }); + + it('should render skeleton placeholder divs', () => { + const { container } = render(); + const mcpIntro = container.querySelector('.MCPIntro'); + const divs = mcpIntro?.querySelectorAll('div'); + expect(divs!.length).toBeGreaterThan(1); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/PostmanRunButton.test.tsx b/__tests__/components/PostmanRunButton.test.tsx new file mode 100644 index 000000000..40818c6fe --- /dev/null +++ b/__tests__/components/PostmanRunButton.test.tsx @@ -0,0 +1,46 @@ +import type { Element } from 'hast'; + +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('PostmanRunButton', () => { + describe('mdxish', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a postman-run-button element', () => { + const { container } = render(); + expect(container.querySelector('.postman-run-button')).toBeInTheDocument(); + }); + + it('should set data attributes for simple props', () => { + const { container } = render(); + const button = container.querySelector('.postman-run-button'); + expect(button).toHaveAttribute('data-postman-action', 'collection/fork'); + expect(button).toHaveAttribute('data-postman-visibility', 'public'); + }); + + it('should pass props through the HAST tree', () => { + const tree = mdxish(md); + const node = tree.children[0] as Element; + expect(node.tagName).toBe('PostmanRunButton'); + expect(node.properties?.action).toBe('collection/fork'); + expect(node.properties?.colLectionId).toBe('123'); + expect(node.properties?.colLectionUrl).toBe('https://example.com'); + expect(node.properties?.visibility).toBe('public'); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/Recipe.test.tsx b/__tests__/components/Recipe.test.tsx new file mode 100644 index 000000000..c6152f4ff --- /dev/null +++ b/__tests__/components/Recipe.test.tsx @@ -0,0 +1,28 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Recipe', () => { + describe('mdxish', () => { + const md = ` + +`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render skeleton placeholder divs', () => { + const { container } = render(); + const divs = container.querySelectorAll('div'); + expect(divs.length).toBeGreaterThan(1); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/Table.test.tsx b/__tests__/components/Table.test.tsx new file mode 100644 index 000000000..89a9e14e4 --- /dev/null +++ b/__tests__/components/Table.test.tsx @@ -0,0 +1,65 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import { mdxish, renderMdxish } from '../../lib'; + +describe('Table', () => { + describe('mdxish', () => { + describe('given a markdown table', () => { + const md = `| col1 | col2 | +|---|---| +| a | b |`; + const mod = renderMdxish(mdxish(md)); + + it('should not error when rendering', () => { + expect(() => render()).not.toThrow(); + }); + + it('should render a rdmd-table wrapper', () => { + const { container } = render(); + expect(container.querySelector('.rdmd-table')).toBeInTheDocument(); + }); + + it('should render a table element', () => { + const { container } = render(); + expect(container.querySelector('table')).toBeInTheDocument(); + }); + + it('should render header cells', () => { + const { container } = render(); + const headers = container.querySelectorAll('th'); + expect(headers).toHaveLength(2); + expect(headers[0]).toHaveTextContent('col1'); + expect(headers[1]).toHaveTextContent('col2'); + }); + + it('should render body cells', () => { + const { container } = render(); + const cells = container.querySelectorAll('td'); + expect(cells).toHaveLength(2); + expect(cells[0]).toHaveTextContent('a'); + expect(cells[1]).toHaveTextContent('b'); + }); + }); + + describe('given a multi-row table', () => { + const md = `| name | value | +|---|---| +| foo | 1 | +| bar | 2 | +| baz | 3 |`; + const mod = renderMdxish(mdxish(md)); + + it('should render all rows', () => { + const { container } = render(); + const rows = container.querySelectorAll('tbody tr'); + expect(rows).toHaveLength(3); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/components/TailwindRoot.test.tsx b/__tests__/components/TailwindRoot.test.tsx new file mode 100644 index 000000000..c5adcbcea --- /dev/null +++ b/__tests__/components/TailwindRoot.test.tsx @@ -0,0 +1,45 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React from 'react'; + +import TailwindRoot from '../../components/TailwindRoot'; + +describe('TailwindRoot', () => { + describe('mdxish', () => { + describe('given flow=true (block-level)', () => { + it('should not error when rendering', () => { + expect(() => render(Content)).not.toThrow(); + }); + + it('should render a div element', () => { + const { container } = render(Content); + const root = container.querySelector('div.readme-tailwind'); + expect(root).toBeInTheDocument(); + }); + + it('should render children', () => { + const { container } = render(Content); + const root = container.querySelector('.readme-tailwind'); + expect(root).toHaveTextContent('Content'); + }); + }); + + describe('given flow=false (inline)', () => { + it('should render a span element', () => { + const { container } = render(Inline); + const root = container.querySelector('span.readme-tailwind'); + expect(root).toBeInTheDocument(); + }); + + it('should render children', () => { + const { container } = render(Inline); + const root = container.querySelector('.readme-tailwind'); + expect(root).toHaveTextContent('Inline'); + }); + }); + }); + + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); +}); diff --git a/__tests__/lib/render-mdxish/Accordion.test.tsx b/__tests__/lib/render-mdxish/Accordion.test.tsx deleted file mode 100644 index 8b121947f..000000000 --- a/__tests__/lib/render-mdxish/Accordion.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Accordion renderer', () => { - describe('given a basic Accordion', () => { - const md = ` -Content -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render a details element with Accordion class', () => { - const { container } = render(); - expect(container.querySelector('details.Accordion')).toBeInTheDocument(); - }); - - it('should render a summary with the title', () => { - const { container } = render(); - const summary = container.querySelector('summary.Accordion-title'); - expect(summary).toBeInTheDocument(); - expect(summary).toHaveTextContent('Title'); - }); - - it('should render children in Accordion-content', () => { - const { container } = render(); - const content = container.querySelector('.Accordion-content'); - expect(content).toBeInTheDocument(); - expect(content).toHaveTextContent('Content'); - }); - }); - - describe('given an Accordion with icon props', () => { - const md = ` -Settings content -`; - const mod = renderMdxish(mdxish(md)); - - it('should render an icon element', () => { - const { container } = render(); - expect(container.querySelector('i.Accordion-icon')).toBeInTheDocument(); - }); - - it('should apply the icon color style', () => { - const { container } = render(); - const icon = container.querySelector('i.Accordion-icon'); - expect(icon).toHaveStyle({ color: '#FF0000' }); - }); - }); -}); diff --git a/__tests__/lib/render-mdxish/Cards.test.tsx b/__tests__/lib/render-mdxish/Cards.test.tsx deleted file mode 100644 index b6ef1a0af..000000000 --- a/__tests__/lib/render-mdxish/Cards.test.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import type { Element } from 'hast'; - -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Cards renderer', () => { - describe('given a Cards with Card children', () => { - const md = ` - - First content - Second content - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render a CardsGrid wrapper', () => { - const { container } = render(); - expect(container.querySelector('.CardsGrid')).toBeInTheDocument(); - }); - - it('should render Card children', () => { - const { container } = render(); - const cards = container.querySelectorAll('.Card'); - expect(cards).toHaveLength(2); - }); - - it('should render card titles', () => { - const { container } = render(); - const titles = container.querySelectorAll('.Card-title'); - expect(titles).toHaveLength(2); - expect(titles[0]).toHaveTextContent('First'); - expect(titles[1]).toHaveTextContent('Second'); - }); - }); - - describe('given a Card with href', () => { - const md = ` - - Linked - -`; - const mod = renderMdxish(mdxish(md)); - - it('should render as an anchor element', () => { - const { container } = render(); - const card = container.querySelector('a.Card'); - expect(card).toBeInTheDocument(); - expect(card).toHaveAttribute('href', 'https://example.com'); - }); - - it('should render the arrow icon', () => { - const { container } = render(); - expect(container.querySelector('.Card-arrow')).toBeInTheDocument(); - }); - }); - - describe('given a Card without href', () => { - const md = ` - - Static - -`; - const mod = renderMdxish(mdxish(md)); - - it('should render as a div element', () => { - const { container } = render(); - const card = container.querySelector('div.Card'); - expect(card).toBeInTheDocument(); - }); - }); - - describe('given a Card with icon and badge props', () => { - const md = ` - - Featured content - -`; - const mod = renderMdxish(mdxish(md)); - - it('should render an icon element', () => { - const { container } = render(); - expect(container.querySelector('.Card-icon')).toBeInTheDocument(); - }); - - it('should render a badge element', () => { - const { container } = render(); - const badge = container.querySelector('.Card-badge'); - expect(badge).toBeInTheDocument(); - expect(badge).toHaveTextContent('New'); - }); - - it('should pass props through the HAST tree', () => { - const tree = mdxish(md); - const cardsNode = tree.children[0] as Element; - const cardChild = cardsNode.children.find( - (child): child is Element => child.type === 'element' && child.tagName === 'Card', - ); - expect(cardChild?.properties?.icon).toBe('fa-star'); - expect(cardChild?.properties?.badge).toBe('New'); - }); - }); -}); diff --git a/__tests__/lib/render-mdxish/Columns.test.tsx b/__tests__/lib/render-mdxish/Columns.test.tsx deleted file mode 100644 index 8d425d68d..000000000 --- a/__tests__/lib/render-mdxish/Columns.test.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import type { Element } from 'hast'; - -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Columns renderer', () => { - describe('given Columns with two Column children', () => { - const md = ` - - Col 1 - Col 2 - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render a Columns wrapper with grid style', () => { - const { container } = render(); - const columns = container.querySelector('.Columns'); - expect(columns).toBeInTheDocument(); - expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(2, auto)' }); - }); - - it('should render Column children', () => { - const { container } = render(); - const cols = container.querySelectorAll('.Column'); - expect(cols).toHaveLength(2); - expect(cols[0]).toHaveTextContent('Col 1'); - expect(cols[1]).toHaveTextContent('Col 2'); - }); - }); - - describe('given Columns with three children', () => { - const md = ` - - A - B - C - -`; - const mod = renderMdxish(mdxish(md)); - - it('should set grid columns based on child count', () => { - const { container } = render(); - const columns = container.querySelector('.Columns'); - expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(3, auto)' }); - }); - }); - - describe('given Columns with the HAST tree', () => { - const md = ` - - Col 1 - Col 2 - -`; - - it('should produce Column children in the tree', () => { - const tree = mdxish(md); - const columnsNode = tree.children[0] as Element; - expect(columnsNode.tagName).toBe('Columns'); - - const columnChildren = columnsNode.children.filter( - (child): child is Element => child.type === 'element' && child.tagName === 'Column', - ); - expect(columnChildren).toHaveLength(2); - }); - }); -}); diff --git a/__tests__/lib/render-mdxish/Embed.test.tsx b/__tests__/lib/render-mdxish/Embed.test.tsx deleted file mode 100644 index 3563930ed..000000000 --- a/__tests__/lib/render-mdxish/Embed.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { Element } from 'hast'; - -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Embed renderer', () => { - describe('given an Embed in link mode', () => { - const md = ` - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render an embed wrapper', () => { - const { container } = render(); - expect(container.querySelector('.embed')).toBeInTheDocument(); - }); - - it('should render an embed-link anchor', () => { - const { container } = render(); - const link = container.querySelector('a.embed-link'); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute('href', 'https://example.com'); - }); - - it('should render the embed title', () => { - const { container } = render(); - const title = container.querySelector('.embed-title'); - expect(title).toBeInTheDocument(); - expect(title).toHaveTextContent('Example'); - }); - }); - - describe('given an Embed in iframe mode', () => { - const md = ` - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render an iframe element', () => { - const { container } = render(); - const iframe = container.querySelector('iframe'); - expect(iframe).toBeInTheDocument(); - expect(iframe).toHaveAttribute('src', 'https://example.com'); - expect(iframe).toHaveAttribute('title', 'Example'); - }); - }); - - describe('given the HAST tree', () => { - const md = ` - -`; - - it('should pass props through the HAST tree', () => { - const tree = mdxish(md); - const node = tree.children[0] as Element; - expect(node.tagName).toBe('embed'); - expect(node.properties?.url).toBe('https://example.com'); - expect(node.properties?.title).toBe('Example'); - }); - }); -}); diff --git a/__tests__/lib/render-mdxish/Heading.test.tsx b/__tests__/lib/render-mdxish/Heading.test.tsx deleted file mode 100644 index 9ca9297d7..000000000 --- a/__tests__/lib/render-mdxish/Heading.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Heading renderer', () => { - describe.each([ - ['# H1', 'h1', 1], - ['## H2', 'h2', 2], - ['### H3', 'h3', 3], - ['#### H4', 'h4', 4], - ['##### H5', 'h5', 5], - ['###### H6', 'h6', 6], - ] as const)('given markdown "%s"', (md, tag, depth) => { - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it(`should render an ${tag} element`, () => { - const { container } = render(); - expect(container.querySelector(tag)).toBeInTheDocument(); - }); - - it('should have the heading class', () => { - const { container } = render(); - expect(container.querySelector(`.heading-${depth}`)).toBeInTheDocument(); - }); - - it('should render the heading text', () => { - const { container } = render(); - const heading = container.querySelector('.heading-text'); - expect(heading).toHaveTextContent(`H${depth}`); - }); - - it('should render an anchor link', () => { - const { container } = render(); - const anchor = container.querySelector('a.heading-anchor-icon'); - expect(anchor).toBeInTheDocument(); - expect(anchor?.getAttribute('href')).toMatch(/^#/); - }); - }); -}); diff --git a/__tests__/lib/render-mdxish/MCPIntro.test.tsx b/__tests__/lib/render-mdxish/MCPIntro.test.tsx deleted file mode 100644 index 84b56f44f..000000000 --- a/__tests__/lib/render-mdxish/MCPIntro.test.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('MCPIntro renderer', () => { - const md = ` - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render a MCPIntro container', () => { - const { container } = render(); - expect(container.querySelector('.MCPIntro')).toBeInTheDocument(); - }); - - it('should render skeleton placeholder divs', () => { - const { container } = render(); - const mcpIntro = container.querySelector('.MCPIntro'); - const divs = mcpIntro?.querySelectorAll('div'); - expect(divs!.length).toBeGreaterThan(1); - }); -}); diff --git a/__tests__/lib/render-mdxish/PostmanRunButton.test.tsx b/__tests__/lib/render-mdxish/PostmanRunButton.test.tsx deleted file mode 100644 index 0f1e0d184..000000000 --- a/__tests__/lib/render-mdxish/PostmanRunButton.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { Element } from 'hast'; - -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('PostmanRunButton renderer', () => { - const md = ` - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render a postman-run-button element', () => { - const { container } = render(); - expect(container.querySelector('.postman-run-button')).toBeInTheDocument(); - }); - - it('should set data attributes for simple props', () => { - const { container } = render(); - const button = container.querySelector('.postman-run-button'); - expect(button).toHaveAttribute('data-postman-action', 'collection/fork'); - expect(button).toHaveAttribute('data-postman-visibility', 'public'); - }); - - it('should pass props through the HAST tree', () => { - const tree = mdxish(md); - const node = tree.children[0] as Element; - expect(node.tagName).toBe('PostmanRunButton'); - expect(node.properties?.action).toBe('collection/fork'); - expect(node.properties?.colLectionId).toBe('123'); - expect(node.properties?.colLectionUrl).toBe('https://example.com'); - expect(node.properties?.visibility).toBe('public'); - }); -}); diff --git a/__tests__/lib/render-mdxish/Recipe.test.tsx b/__tests__/lib/render-mdxish/Recipe.test.tsx deleted file mode 100644 index 89ac5a00d..000000000 --- a/__tests__/lib/render-mdxish/Recipe.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Recipe renderer', () => { - const md = ` - -`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render skeleton placeholder divs', () => { - const { container } = render(); - const divs = container.querySelectorAll('div'); - expect(divs.length).toBeGreaterThan(1); - }); -}); diff --git a/__tests__/lib/render-mdxish/Table.test.tsx b/__tests__/lib/render-mdxish/Table.test.tsx deleted file mode 100644 index 7390b2c7f..000000000 --- a/__tests__/lib/render-mdxish/Table.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import { mdxish, renderMdxish } from '../../../lib'; - -describe('Table renderer', () => { - describe('given a markdown table', () => { - const md = `| col1 | col2 | -|---|---| -| a | b |`; - const mod = renderMdxish(mdxish(md)); - - it('should not error when rendering', () => { - expect(() => render()).not.toThrow(); - }); - - it('should render a rdmd-table wrapper', () => { - const { container } = render(); - expect(container.querySelector('.rdmd-table')).toBeInTheDocument(); - }); - - it('should render a table element', () => { - const { container } = render(); - expect(container.querySelector('table')).toBeInTheDocument(); - }); - - it('should render header cells', () => { - const { container } = render(); - const headers = container.querySelectorAll('th'); - expect(headers).toHaveLength(2); - expect(headers[0]).toHaveTextContent('col1'); - expect(headers[1]).toHaveTextContent('col2'); - }); - - it('should render body cells', () => { - const { container } = render(); - const cells = container.querySelectorAll('td'); - expect(cells).toHaveLength(2); - expect(cells[0]).toHaveTextContent('a'); - expect(cells[1]).toHaveTextContent('b'); - }); - }); - - describe('given a multi-row table', () => { - const md = `| name | value | -|---|---| -| foo | 1 | -| bar | 2 | -| baz | 3 |`; - const mod = renderMdxish(mdxish(md)); - - it('should render all rows', () => { - const { container } = render(); - const rows = container.querySelectorAll('tbody tr'); - expect(rows).toHaveLength(3); - }); - }); -}); diff --git a/__tests__/lib/render-mdxish/TailwindRoot.test.tsx b/__tests__/lib/render-mdxish/TailwindRoot.test.tsx deleted file mode 100644 index b616012a5..000000000 --- a/__tests__/lib/render-mdxish/TailwindRoot.test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import React from 'react'; - -import TailwindRoot from '../../../components/TailwindRoot'; - -describe('TailwindRoot renderer', () => { - describe('given flow=true (block-level)', () => { - it('should not error when rendering', () => { - expect(() => render(Content)).not.toThrow(); - }); - - it('should render a div element', () => { - const { container } = render(Content); - const root = container.querySelector('div.readme-tailwind'); - expect(root).toBeInTheDocument(); - }); - - it('should render children', () => { - const { container } = render(Content); - const root = container.querySelector('.readme-tailwind'); - expect(root).toHaveTextContent('Content'); - }); - }); - - describe('given flow=false (inline)', () => { - it('should render a span element', () => { - const { container } = render(Inline); - const root = container.querySelector('span.readme-tailwind'); - expect(root).toBeInTheDocument(); - }); - - it('should render children', () => { - const { container } = render(Inline); - const root = container.querySelector('.readme-tailwind'); - expect(root).toHaveTextContent('Inline'); - }); - }); -}); From 19e4b312718844c4ea4660e159816278ca1c78e7 Mon Sep 17 00:00:00 2001 From: Maximilian Falco Widjaya Date: Mon, 23 Feb 2026 16:25:48 +1100 Subject: [PATCH 03/12] test: scaffold and standardise component test structure --- __tests__/components/Accordion.test.tsx | 4 + __tests__/components/Anchor.test.tsx | 112 ++++++++------- __tests__/components/Callout.test.tsx | 46 +++--- __tests__/components/Cards.test.tsx | 4 + __tests__/components/Code.test.tsx | 46 +++--- __tests__/components/CodeTabs.test.tsx | 22 ++- __tests__/components/Columns.test.tsx | 4 + __tests__/components/Embed.test.tsx | 4 + __tests__/components/Glossary.test.tsx | 20 ++- __tests__/components/HTMLBlock.test.tsx | 88 ++++++------ __tests__/components/Heading.test.tsx | 4 + __tests__/components/Image.test.tsx | 98 +++++++------ __tests__/components/MCPIntro.test.tsx | 4 + .../components/PostmanRunButton.test.tsx | 4 + __tests__/components/Recipe.test.tsx | 4 + __tests__/components/Table.test.tsx | 4 + __tests__/components/TableOfContents.test.jsx | 134 ++++++++++-------- __tests__/components/TailwindRoot.test.tsx | 4 + __tests__/components/Variable.test.tsx | 20 ++- .../TableOfContents.test.jsx.snap | 12 -- .../__snapshots__/index.test.ts.snap | 12 +- __tests__/components/index.test.ts | 108 +++++++------- 22 files changed, 441 insertions(+), 317 deletions(-) delete mode 100644 __tests__/components/__snapshots__/TableOfContents.test.jsx.snap diff --git a/__tests__/components/Accordion.test.tsx b/__tests__/components/Accordion.test.tsx index f50403132..cd2deff23 100644 --- a/__tests__/components/Accordion.test.tsx +++ b/__tests__/components/Accordion.test.tsx @@ -58,4 +58,8 @@ describe('Accordion', () => { describe('mdx', () => { it.todo('should render through the mdx pipeline'); }); + + describe('render', () => { + it.todo('should render the component directly'); + }); }); diff --git a/__tests__/components/Anchor.test.tsx b/__tests__/components/Anchor.test.tsx index c5c715b83..d8c8c895a 100644 --- a/__tests__/components/Anchor.test.tsx +++ b/__tests__/components/Anchor.test.tsx @@ -4,63 +4,71 @@ import React from 'react'; import Anchor from '../../components/Anchor'; describe('Anchor', () => { - it('renders a basic anchor', () => { - render(Click me); + describe('mdxish', () => { + it.todo('should render through the mdxish pipeline'); + }); - expect(screen.getByRole('link')).toMatchInlineSnapshot(` - - Click me - - `); + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); }); - it('unwraps nested anchor elements', () => { - // Simulates what happens when GFM autolinks URL-like text inside an Anchor - const { container } = render( - - https://example.com - , - ); + describe('render', () => { + it('renders a basic anchor', () => { + render(Click me); - // Should only have one tag, not nested - const anchors = container.querySelectorAll('a'); - expect(anchors).toHaveLength(1); - expect(anchors[0]).toMatchInlineSnapshot(` - - https://example.com - - `); - }); + expect(screen.getByRole('link')).toMatchInlineSnapshot(` + + Click me + + `); + }); + + it('unwraps nested anchor elements', () => { + const { container } = render( + + https://example.com + , + ); + + const anchors = container.querySelectorAll('a'); + expect(anchors).toHaveLength(1); + expect(anchors[0]).toMatchInlineSnapshot(` + + https://example.com + + `); + }); - it('preserves non-anchor children', () => { - render( - - Bold and italic - , - ); + it('preserves non-anchor children', () => { + render( + + Bold and italic + , + ); - expect(screen.getByRole('link')).toMatchInlineSnapshot(` - - - Bold - - and - - italic - - - `); + expect(screen.getByRole('link')).toMatchInlineSnapshot(` + + + Bold + + and + + italic + + + `); + }); }); }); diff --git a/__tests__/components/Callout.test.tsx b/__tests__/components/Callout.test.tsx index 2b894d7dc..50544b263 100644 --- a/__tests__/components/Callout.test.tsx +++ b/__tests__/components/Callout.test.tsx @@ -4,27 +4,37 @@ import React from 'react'; import Callout from '../../components/Callout'; describe('Callout', () => { - it('render _all_ its children', () => { - render( - -

Title

-

First Paragraph

-

Second Paragraph

-
, - ); + describe('mdxish', () => { + it.todo('should render through the mdxish pipeline'); + }); - expect(screen.getByText('Second Paragraph')).toBeVisible(); + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); }); - it("doesn't render all its children if it's **empty**", () => { - render( - -

Title

-

First Paragraph

-

Second Paragraph

-
, - ); + describe('render', () => { + it('render _all_ its children', () => { + render( + +

Title

+

First Paragraph

+

Second Paragraph

+
, + ); + + expect(screen.getByText('Second Paragraph')).toBeVisible(); + }); + + it("doesn't render all its children if it's **empty**", () => { + render( + +

Title

+

First Paragraph

+

Second Paragraph

+
, + ); - expect(screen.queryByText('Title')).toBeNull(); + expect(screen.queryByText('Title')).toBeNull(); + }); }); }); diff --git a/__tests__/components/Cards.test.tsx b/__tests__/components/Cards.test.tsx index ef2757d1e..8b3ea8e9a 100644 --- a/__tests__/components/Cards.test.tsx +++ b/__tests__/components/Cards.test.tsx @@ -112,4 +112,8 @@ describe('Cards', () => { describe('mdx', () => { it.todo('should render through the mdx pipeline'); }); + + describe('render', () => { + it.todo('should render the component directly'); + }); }); diff --git a/__tests__/components/Code.test.tsx b/__tests__/components/Code.test.tsx index d581bd9d2..558f19e77 100644 --- a/__tests__/components/Code.test.tsx +++ b/__tests__/components/Code.test.tsx @@ -18,30 +18,40 @@ vi.mock('@readme/syntax-highlighter', () => ({ canonical: lang => lang, })); -describe.skip('Code', () => { - it.skip('copies the variable interpolated code', () => { - const props = { - children: ['console.log("<>");'], - }; +describe('Code', () => { + describe('mdxish', () => { + it.todo('should render through the mdxish pipeline'); + }); - const { container } = render(); + describe('mdx', () => { + it.todo('should render through the mdx pipeline'); + }); - expect(container).toHaveTextContent(/VARIABLE_SUBSTITUTED/); - fireEvent.click(screen.getByRole('button')); + describe.skip('render', () => { + it.skip('copies the variable interpolated code', () => { + const props = { + children: ['console.log("<>");'], + }; - expect(copy).toHaveBeenCalledWith(expect.stringMatching(/VARIABLE_SUBSTITUTED/)); - }); + const { container } = render(); - it.skip('does not nest the button inside the code block', () => { - render({'console.log("hi");'}); - const btn = screen.getByRole('button'); + expect(container).toHaveTextContent(/VARIABLE_SUBSTITUTED/); + fireEvent.click(screen.getByRole('button')); - expect(btn.parentNode?.nodeName.toLowerCase()).not.toBe('code'); - }); + expect(copy).toHaveBeenCalledWith(expect.stringMatching(/VARIABLE_SUBSTITUTED/)); + }); + + it.skip('does not nest the button inside the code block', () => { + render({'console.log("hi");'}); + const btn = screen.getByRole('button'); + + expect(btn.parentNode?.nodeName.toLowerCase()).not.toBe('code'); + }); - it.skip('allows undefined children?!', () => { - const { container } = render(); + it.skip('allows undefined children?!', () => { + const { container } = render(); - expect(container).toHaveTextContent(''); + expect(container).toHaveTextContent(''); + }); }); }); diff --git a/__tests__/components/CodeTabs.test.tsx b/__tests__/components/CodeTabs.test.tsx index eb2d420e5..9e31e5457 100644 --- a/__tests__/components/CodeTabs.test.tsx +++ b/__tests__/components/CodeTabs.test.tsx @@ -4,8 +4,13 @@ import React from 'react'; import { execute } from '../helpers'; describe('CodeTabs', () => { - it.skip('render _all_ its children', () => { - const md = ` + describe('mdxish', () => { + it.todo('should render through the mdxish pipeline'); + }); + + describe('mdx', () => { + it.skip('render _all_ its children', () => { + const md = ` \`\`\` assert('theme', 'dark'); \`\`\` @@ -13,10 +18,15 @@ assert('theme', 'dark'); assert('theme', 'light'); \`\`\` `; - const Component = execute(md); - const { container } = render(); + const Component = execute(md); + const { container } = render(); + + expect(container).toHaveTextContent("assert('theme', 'dark')"); + expect(container).toHaveTextContent("assert('theme', 'light')"); + }); + }); - expect(container).toHaveTextContent("assert('theme', 'dark')"); - expect(container).toHaveTextContent("assert('theme', 'light')"); + describe('render', () => { + it.todo('should render the component directly'); }); }); diff --git a/__tests__/components/Columns.test.tsx b/__tests__/components/Columns.test.tsx index ff868604f..d36ab2800 100644 --- a/__tests__/components/Columns.test.tsx +++ b/__tests__/components/Columns.test.tsx @@ -78,4 +78,8 @@ describe('Columns', () => { describe('mdx', () => { it.todo('should render through the mdx pipeline'); }); + + describe('render', () => { + it.todo('should render the component directly'); + }); }); diff --git a/__tests__/components/Embed.test.tsx b/__tests__/components/Embed.test.tsx index a7c921e9a..6c92ae10d 100644 --- a/__tests__/components/Embed.test.tsx +++ b/__tests__/components/Embed.test.tsx @@ -75,4 +75,8 @@ describe('Embed', () => { describe('mdx', () => { it.todo('should render through the mdx pipeline'); }); + + describe('render', () => { + it.todo('should render the component directly'); + }); }); diff --git a/__tests__/components/Glossary.test.tsx b/__tests__/components/Glossary.test.tsx index f190f6539..d7d26fbf5 100644 --- a/__tests__/components/Glossary.test.tsx +++ b/__tests__/components/Glossary.test.tsx @@ -4,11 +4,21 @@ import React from 'react'; import { execute } from '../helpers'; describe('Glossary', () => { - it('renders a glossary item', () => { - const md = 'parliament'; - const Content = execute(md); - render(); + describe('mdxish', () => { + it.todo('should render through the mdxish pipeline'); + }); + + describe('mdx', () => { + it('renders a glossary item', () => { + const md = 'parliament'; + const Content = execute(md); + render(); + + expect(screen.getByText('parliament')).toBeVisible(); + }); + }); - expect(screen.getByText('parliament')).toBeVisible(); + describe('render', () => { + it.todo('should render the component directly'); }); }); diff --git a/__tests__/components/HTMLBlock.test.tsx b/__tests__/components/HTMLBlock.test.tsx index b080762e8..59dbf7027 100644 --- a/__tests__/components/HTMLBlock.test.tsx +++ b/__tests__/components/HTMLBlock.test.tsx @@ -8,57 +8,65 @@ import HTMLBlock from '../../components/HTMLBlock'; import { execute } from '../helpers'; describe('HTML Block', () => { - beforeEach(() => { - global.mockFn = vi.fn(); + describe('mdxish', () => { + it.todo('should render through the mdxish pipeline'); }); - afterEach(() => { - cleanup(); - vi.restoreAllMocks(); + describe('mdx', () => { + it('renders the html in a `
` tag if safeMode={true}', () => {
+      const md = '{`