diff --git a/__tests__/Glossary.test.tsx b/__tests__/Glossary.test.tsx
deleted file mode 100644
index 691f2a73e..000000000
--- a/__tests__/Glossary.test.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { render, fireEvent, screen } from '@testing-library/react';
-import React from 'react';
-
-import { Glossary } from '../components/Glossary';
-
-test('should output a glossary item if the term exists', () => {
- const term = 'acme';
- const definition = 'This is a definition';
- const { container } = render(acme);
-
- const trigger = container.querySelector('.GlossaryItem-trigger');
- expect(trigger).toHaveTextContent(term);
- if (trigger) {
- fireEvent.mouseEnter(trigger);
- }
- const tooltipContent = screen.getByText(definition, { exact: false });
- expect(tooltipContent).toHaveTextContent(`${term} - ${definition}`);
-});
-
-test('should be case insensitive', () => {
- const term = 'aCme';
- const definition = 'This is a definition';
- const { container } = render(acme);
-
- const trigger = container.querySelector('.GlossaryItem-trigger');
- expect(trigger).toHaveTextContent('acme');
- if (trigger) {
- fireEvent.mouseEnter(trigger);
- }
- const tooltipContent = screen.getByText(definition, { exact: false });
- expect(tooltipContent).toHaveTextContent(`${term} - ${definition}`);
-});
-
-test('should output the term if the definition does not exist', () => {
- const term = 'something';
- const { container } = render({term});
-
- expect(container.querySelector('.GlossaryItem-trigger')).not.toBeInTheDocument();
- expect(container.querySelector('span')).toHaveTextContent(term);
-});
diff --git a/__tests__/__snapshots__/compilers.test.ts.snap b/__tests__/__snapshots__/compilers.test.ts.snap
deleted file mode 100644
index b8522bf29..000000000
--- a/__tests__/__snapshots__/compilers.test.ts.snap
+++ /dev/null
@@ -1,6 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`ReadMe Flavored Blocks > Embed 1`] = `
-"[Embedded meta links.](https://nyti.me/s/gzoa2xb2v3 "@embed")
-"
-`;
diff --git a/__tests__/__snapshots__/index.test.js.snap b/__tests__/__snapshots__/index.test.js.snap
deleted file mode 100644
index b04a9ad03..000000000
--- a/__tests__/__snapshots__/index.test.js.snap
+++ /dev/null
@@ -1,348 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`anchor target: should allow _blank if using HTML 1`] = `"
test
"`;
-
-exports[`anchor target: should allow download if using HTML 1`] = `"test
"`;
-
-exports[`anchor target: should default to _self 1`] = `"test
"`;
-
-exports[`anchors 1`] = `
-"link
-xss
-doc
-ref
-blog
-changelog
-page
"
-`;
-
-exports[`anchors with baseUrl 1`] = `
-"<p><a href=\\"doc:slug\\">doc</a><br>
-<a href=\\"ref:slug\\">ref</a><br>
-<a href=\\"blog:slug\\">blog</a><br>
-<a href=\\"changelog:slug\\">changelog</a><br>
-<a href=\\"page:slug\\">page</a></p>"
-`;
-
-exports[`anchors with baseUrl and special characters in url hash 1`] = `"ref
"`;
-
-exports[`check list items 1`] = `
-""
-`;
-
-exports[`code samples > should parse indented code on the first line 1`] = `
-{
- "children": [
- {
- "children": [
- {
- "position": {
- "end": {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "start": {
- "column": 5,
- "line": 1,
- "offset": 4,
- },
- },
- "type": "text",
- "value": "const code = true;",
- },
- ],
- "position": {
- "end": {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "start": {
- "column": 5,
- "line": 1,
- "offset": 4,
- },
- },
- "type": "paragraph",
- },
- ],
- "position": {
- "end": {
- "column": 23,
- "line": 1,
- "offset": 22,
- },
- "start": {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": "root",
-}
-`;
-
-exports[`emojis 1`] = `
-"
-
-:unknown-emoji:
"
-`;
-
-exports[`export multiple Markdown renderers > allows complex compact headings 1`] = `undefined`;
-
-exports[`export multiple Markdown renderers > renders HTML 1`] = `undefined`;
-
-exports[`export multiple Markdown renderers > renders MD 1`] = `
-"# Hello World
-"
-`;
-
-exports[`export multiple Markdown renderers > renders custom React components 1`] = `[Function]`;
-
-exports[`export multiple Markdown renderers > renders hAST 1`] = `undefined`;
-
-exports[`export multiple Markdown renderers > renders mdAST 1`] = `
-{
- "children": [
- {
- "children": [
- {
- "position": {
- "end": {
- "column": 14,
- "line": 1,
- "offset": 13,
- },
- "start": {
- "column": 3,
- "line": 1,
- "offset": 2,
- },
- },
- "type": "text",
- "value": "Hello World",
- },
- ],
- "depth": 1,
- "position": {
- "end": {
- "column": 14,
- "line": 1,
- "offset": 13,
- },
- "start": {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": "heading",
- },
- {
- "children": [
- {
- "position": {
- "end": {
- "column": 34,
- "line": 7,
- "offset": 184,
- },
- "start": {
- "column": 3,
- "line": 3,
- "offset": 17,
- },
- },
- "type": "text",
- "value": "| Col. A | Col. B | Col. C |
-|:-------:|:-------:|:-------:|
-| Cell A1 | Cell B1 | Cell C1 |
-| Cell A2 | Cell B2 | Cell C2 |
-| Cell A3 | Cell B3 | Cell C3 |",
- },
- ],
- "position": {
- "end": {
- "column": 34,
- "line": 7,
- "offset": 184,
- },
- "start": {
- "column": 3,
- "line": 3,
- "offset": 17,
- },
- },
- "type": "paragraph",
- },
- {
- "children": [
- {
- "children": [
- {
- "position": {
- "end": {
- "column": 15,
- "line": 9,
- "offset": 200,
- },
- "start": {
- "column": 4,
- "line": 9,
- "offset": 189,
- },
- },
- "type": "text",
- "value": "Embed Title",
- },
- ],
- "position": {
- "end": {
- "column": 67,
- "line": 9,
- "offset": 252,
- },
- "start": {
- "column": 3,
- "line": 9,
- "offset": 188,
- },
- },
- "title": "@embed",
- "type": "link",
- "url": "https://jsfiddle.net/rafegoldberg/5VA5j/",
- },
- ],
- "position": {
- "end": {
- "column": 67,
- "line": 9,
- "offset": 252,
- },
- "start": {
- "column": 3,
- "line": 9,
- "offset": 188,
- },
- },
- "type": "paragraph",
- },
- {
- "children": [
- {
- "children": [
- {
- "position": {
- "end": {
- "column": 12,
- "line": 11,
- "offset": 265,
- },
- "start": {
- "column": 5,
- "line": 11,
- "offset": 258,
- },
- },
- "type": "text",
- "value": "βοΈ UhOh",
- },
- ],
- "position": {
- "end": {
- "column": 12,
- "line": 11,
- "offset": 265,
- },
- "start": {
- "column": 5,
- "line": 11,
- "offset": 258,
- },
- },
- "type": "paragraph",
- },
- {
- "children": [
- {
- "position": {
- "end": {
- "column": 61,
- "line": 13,
- "offset": 330,
- },
- "start": {
- "column": 5,
- "line": 13,
- "offset": 274,
- },
- },
- "type": "text",
- "value": "Lorem ipsum dolor sit amet consectetur adipisicing elit.",
- },
- ],
- "position": {
- "end": {
- "column": 61,
- "line": 13,
- "offset": 330,
- },
- "start": {
- "column": 5,
- "line": 13,
- "offset": 274,
- },
- },
- "type": "paragraph",
- },
- ],
- "position": {
- "end": {
- "column": 61,
- "line": 13,
- "offset": 330,
- },
- "start": {
- "column": 3,
- "line": 11,
- "offset": 256,
- },
- },
- "type": "blockquote",
- },
- ],
- "position": {
- "end": {
- "column": 3,
- "line": 16,
- "offset": 335,
- },
- "start": {
- "column": 1,
- "line": 1,
- "offset": 0,
- },
- },
- "type": "root",
-}
-`;
-
-exports[`export multiple Markdown renderers > renders plain markdown as React 1`] = `undefined`;
-
-exports[`heading 1`] = `"Example Header
"`;
-
-exports[`image 1`] = `"
"`;
-
-exports[`list items 1`] = `
-""
-`;
-
-exports[`prefix anchors with "section-" > should add a section- prefix to heading anchors 1`] = `undefined`;
-
-exports[`tables 1`] = `"| Tables | Are | Cool |
|---|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
"`;
diff --git a/__tests__/__snapshots__/link-parsers.test.js.snap b/__tests__/__snapshots__/link-parsers.test.js.snap
deleted file mode 100644
index a29f2b323..000000000
--- a/__tests__/__snapshots__/link-parsers.test.js.snap
+++ /dev/null
@@ -1,178 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`a bare autoLinked url 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "http://www.googl.com",
- },
- ],
- "title": null,
- "type": "link",
- "url": "http://www.googl.com",
- },
- ],
- "type": "paragraph",
- },
- ],
- "type": "root",
-}
-`;
-
-exports[`a bare autoLinked url with no protocol 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "www.google.com",
- },
- ],
- "title": null,
- "type": "link",
- "url": "http://www.google.com",
- },
- ],
- "type": "paragraph",
- },
- ],
- "type": "root",
-}
-`;
-
-exports[`a bracketed autoLinked url 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "http://www.google.com",
- },
- ],
- "title": null,
- "type": "link",
- "url": "http://www.google.com",
- },
- ],
- "type": "paragraph",
- },
- ],
- "type": "root",
-}
-`;
-
-exports[`a link ref 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "link",
- },
- ],
- "identifier": "link",
- "label": "link",
- "referenceType": "shortcut",
- "type": "linkReference",
- },
- ],
- "type": "paragraph",
- },
- ],
- "type": "root",
-}
-`;
-
-exports[`a link ref with reference 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "link",
- },
- ],
- "identifier": "link",
- "label": "link",
- "referenceType": "shortcut",
- "type": "linkReference",
- },
- ],
- "type": "paragraph",
- },
- Object {
- "identifier": "link",
- "label": "link",
- "title": null,
- "type": "definition",
- "url": "www.example.com",
- },
- ],
- "type": "root",
-}
-`;
-
-exports[`a link with label 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "link",
- },
- ],
- "title": null,
- "type": "link",
- "url": "http://www.foo.com",
- },
- ],
- "type": "paragraph",
- },
- ],
- "type": "root",
-}
-`;
-
-exports[`a link with no url 1`] = `
-Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "children": Array [
- Object {
- "type": "text",
- "value": "link",
- },
- ],
- "title": null,
- "type": "link",
- "url": "",
- },
- ],
- "type": "paragraph",
- },
- ],
- "type": "root",
-}
-`;
diff --git a/__tests__/compilers.test.ts b/__tests__/compilers.test.ts
deleted file mode 100644
index 3861b22c0..000000000
--- a/__tests__/compilers.test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type { Element } from 'hast';
-
-import { mdast, mdx, mdxish } from '../index';
-
-describe('ReadMe Flavored Blocks', () => {
- it('Embed', () => {
- const txt = '[Embedded meta links.](https://nyti.me/s/gzoa2xb2v3 "@embed")';
- const ast = mdast(txt);
- const out = mdx(ast);
- expect(out).toMatchSnapshot();
- });
-
- it('Emojis', () => {
- expect(mdx(mdast(':smiley:'))).toMatchInlineSnapshot(`
- ":smiley:
- "
- `);
- });
-});
-
-describe('mdxish ReadMe Flavored Blocks', () => {
- it('Embed', () => {
- const txt = '[Embedded meta links.](https://nyti.me/s/gzoa2xb2v3 "@embed")';
- const hast = mdxish(txt);
- const embed = hast.children[0] as Element;
-
- expect(embed.type).toBe('element');
- expect(embed.tagName).toBe('embed');
- expect(embed.properties.url).toBe('https://nyti.me/s/gzoa2xb2v3');
- expect(embed.properties.title).toBe('Embedded meta links.');
- });
-
- it('Emojis', () => {
- const hast = mdxish(':smiley:');
- const paragraph = hast.children[0] as Element;
-
- expect(paragraph.type).toBe('element');
- expect(paragraph.tagName).toBe('p');
- // gemojiTransformer converts :smiley: to π
- const textNode = paragraph.children[0];
- expect(textNode.type).toBe('text');
- expect('value' in textNode && textNode.value).toBe('π');
- });
-});
diff --git a/__tests__/compilers/gemoji.test.ts b/__tests__/compilers/gemoji.test.ts
index d4765e2df..0d1e1f797 100644
--- a/__tests__/compilers/gemoji.test.ts
+++ b/__tests__/compilers/gemoji.test.ts
@@ -9,6 +9,13 @@ describe('gemoji compiler', () => {
expect(mdx(mdast(markdown)).trimEnd()).toStrictEqual(markdown);
});
+ it('should compile :smiley: back to a shortcode', () => {
+ expect(mdx(mdast(':smiley:'))).toMatchInlineSnapshot(`
+ ":smiley:
+ "
+ `);
+ });
+
it('should compile owlmoji back to a shortcode', () => {
const markdown = ':owlbert:';
@@ -23,6 +30,17 @@ describe('gemoji compiler', () => {
});
describe('mdxish gemoji compiler', () => {
+ it('should convert :smiley: to emoji character', () => {
+ const hast = mdxish(':smiley:');
+ const paragraph = hast.children[0] as Element;
+
+ expect(paragraph.type).toBe('element');
+ expect(paragraph.tagName).toBe('p');
+ const textNode = paragraph.children[0];
+ expect(textNode.type).toBe('text');
+ expect('value' in textNode && textNode.value).toBe('π');
+ });
+
it('should convert gemojis to emoji nodes', () => {
const markdown = 'This is a gemoji :joy:.';
diff --git a/__tests__/components/Accordion.test.tsx b/__tests__/components/Accordion.test.tsx
new file mode 100644
index 000000000..7537d8921
--- /dev/null
+++ b/__tests__/components/Accordion.test.tsx
@@ -0,0 +1,101 @@
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
+import React from 'react';
+
+import Accordion from '../../components/Accordion';
+import { mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
+
+describe('Accordion', () => {
+ describe('mdxish', () => {
+ describe('given a basic Accordion', () => {
+ const md = `
+Content
+`;
+ const mod = renderMdxish(mdxish(md));
+
+ 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('renders an accordion', () => {
+ const md = 'Content';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('details.Accordion')).toBeInTheDocument();
+ expect(container.querySelector('summary.Accordion-title')).toHaveTextContent('Title');
+ expect(container.querySelector('.Accordion-content')).toHaveTextContent('Content');
+ });
+
+ it('renders an accordion with icon props', () => {
+ const md = 'Settings content';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('i.Accordion-icon')).toBeInTheDocument();
+ expect(container.querySelector('i.Accordion-icon')).toHaveStyle({ color: '#FF0000' });
+ });
+ });
+
+ describe('render', () => {
+ it('renders a details element with Accordion class', () => {
+ const { container } = render(Content);
+ expect(container.querySelector('details.Accordion')).toBeInTheDocument();
+ });
+
+ it('renders a summary with the title', () => {
+ const { container } = render(Content);
+ const summary = container.querySelector('summary.Accordion-title');
+ expect(summary).toHaveTextContent('Title');
+ });
+
+ it('renders children in Accordion-content', () => {
+ const { container } = render(Content);
+ expect(container.querySelector('.Accordion-content')).toHaveTextContent('Content');
+ });
+
+ it('renders an icon when icon prop is provided', () => {
+ const { container } = render(Content);
+ const icon = container.querySelector('i.Accordion-icon');
+ expect(icon).toBeInTheDocument();
+ expect(icon).toHaveStyle({ color: '#FF0000' });
+ });
+ });
+});
diff --git a/__tests__/components/Anchor.test.tsx b/__tests__/components/Anchor.test.tsx
index c5c715b83..2e4953d25 100644
--- a/__tests__/components/Anchor.test.tsx
+++ b/__tests__/components/Anchor.test.tsx
@@ -1,66 +1,105 @@
+import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import Anchor from '../../components/Anchor';
+import { mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
describe('Anchor', () => {
- it('renders a basic anchor', () => {
- render(Click me);
+ describe('mdxish', () => {
+ it('renders a markdown link', () => {
+ const md = '[Example](https://example.com)';
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
- expect(screen.getByRole('link')).toMatchInlineSnapshot(`
-
- Click me
-
- `);
+ const link = container.querySelector('a');
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ expect(link).toHaveTextContent('Example');
+ });
+
+ it('renders an autolink', () => {
+ const md = '';
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ const link = container.querySelector('a');
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ });
});
- it('unwraps nested anchor elements', () => {
- // Simulates what happens when GFM autolinks URL-like text inside an Anchor
- const { container } = render(
-
- https://example.com
- ,
- );
+ describe('mdx', () => {
+ it('renders a markdown link', () => {
+ const md = '[Example](https://example.com)';
+ const Content = execute(md);
+ const { container } = render();
- // Should only have one tag, not nested
- const anchors = container.querySelectorAll('a');
- expect(anchors).toHaveLength(1);
- expect(anchors[0]).toMatchInlineSnapshot(`
-
- https://example.com
-
- `);
+ const link = container.querySelector('a');
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ expect(link).toHaveTextContent('Example');
+ });
});
- it('preserves non-anchor children', () => {
- render(
-
- Bold and italic
- ,
- );
+ describe('render', () => {
+ it('renders a basic anchor', () => {
+ render(Click me);
+
+ 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
+ ,
+ );
- 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..9a2b031e8 100644
--- a/__tests__/components/Callout.test.tsx
+++ b/__tests__/components/Callout.test.tsx
@@ -1,30 +1,101 @@
+import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import Callout from '../../components/Callout';
+import { mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
describe('Callout', () => {
- it('render _all_ its children', () => {
- render(
-
- Title
- First Paragraph
- Second Paragraph
- ,
- );
-
- expect(screen.getByText('Second Paragraph')).toBeVisible();
+ describe('mdxish', () => {
+ it('renders a callout with emoji and title', () => {
+ const md = `> βοΈ Error Callout
+>
+> Something went wrong.`;
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ expect(container.querySelector('.callout')).toBeInTheDocument();
+ expect(container.textContent).toContain('Error Callout');
+ expect(container.textContent).toContain('Something went wrong.');
+ });
+
+ it('renders a callout with no title', () => {
+ const md = `> π§
+>
+> Callout content`;
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ expect(container.querySelector('.callout-heading.empty')).toBeInTheDocument();
+ expect(container.textContent).toContain('Callout content');
+ });
+
+ it('renders a regular blockquote without emoji', () => {
+ const md = '> Hello world';
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ expect(container.querySelector('blockquote')).toBeInTheDocument();
+ expect(container.querySelector('.callout')).not.toBeInTheDocument();
+ expect(container.textContent).toContain('Hello world');
+ });
+ });
+
+ describe('mdx', () => {
+ it('renders a callout with emoji and title', () => {
+ const md = '> \u2757\uFE0F Error Callout\n>\n> Something went wrong.';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('.callout')).toBeInTheDocument();
+ expect(container.textContent).toContain('Error Callout');
+ expect(container.textContent).toContain('Something went wrong.');
+ });
+
+ it('renders a callout with no title', () => {
+ const md = '> \uD83D\uDEA7\n>\n> Callout with no title.';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('.callout-heading.empty')).toBeInTheDocument();
+ expect(container.innerHTML).toMatchInlineSnapshot(
+ `"π§Callout with no title.
"`,
+ );
+ });
+
+ it('renders an error callout', () => {
+ const md = '> \u2757\uFE0F Error Callout\n>\n> Lorem ipsum dolor.';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.innerHTML).toMatchSnapshot();
+ });
});
- 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
new file mode 100644
index 000000000..31be0b776
--- /dev/null
+++ b/__tests__/components/Cards.test.tsx
@@ -0,0 +1,184 @@
+import type { Element } from 'hast';
+
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
+import React from 'react';
+
+import CardsGrid, { Card } from '../../components/Cards';
+import { mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
+
+describe('Cards', () => {
+ describe('mdxish', () => {
+ describe('given a Cards with Card children', () => {
+ const md = `
+
+ First content
+ Second content
+
+`;
+ const mod = renderMdxish(mdxish(md));
+
+ 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('renders Cards with Card children', () => {
+ const md = 'ContentMore';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('.CardsGrid')).toBeInTheDocument();
+ const cards = container.querySelectorAll('.Card');
+ expect(cards).toHaveLength(2);
+ });
+
+ it('renders Card as an anchor when href is provided', () => {
+ const md = 'Linked';
+ const Content = execute(md);
+ const { container } = render();
+
+ const card = container.querySelector('a.Card');
+ expect(card).toBeInTheDocument();
+ expect(card).toHaveAttribute('href', 'https://example.com');
+ });
+ });
+
+ describe('render', () => {
+ it('renders a CardsGrid wrapper', () => {
+ const { container } = render(
+
+ Content
+ ,
+ );
+ expect(container.querySelector('.CardsGrid')).toBeInTheDocument();
+ });
+
+ it('renders Card children with titles', () => {
+ const { container } = render(
+
+ First
+ Second
+ ,
+ );
+ const cards = container.querySelectorAll('.Card');
+ expect(cards).toHaveLength(2);
+ expect(container.querySelectorAll('.Card-title')[0]).toHaveTextContent('First');
+ });
+
+ it('renders Card as an anchor when href is provided', () => {
+ const { container } = render(
+
+ Linked
+ ,
+ );
+ const card = container.querySelector('a.Card');
+ expect(card).toHaveAttribute('href', 'https://example.com');
+ expect(container.querySelector('.Card-arrow')).toBeInTheDocument();
+ });
+
+ it('renders Card as a div when no href', () => {
+ const { container } = render(
+
+ Static
+ ,
+ );
+ expect(container.querySelector('div.Card')).toBeInTheDocument();
+ });
+
+ it('renders icon and badge props', () => {
+ const { container } = render(
+
+ Content
+ ,
+ );
+ expect(container.querySelector('.Card-icon')).toBeInTheDocument();
+ expect(container.querySelector('.Card-badge')).toHaveTextContent('New');
+ });
+ });
+});
diff --git a/__tests__/components/Code.test.tsx b/__tests__/components/Code.test.tsx
index d581bd9d2..31a2d7648 100644
--- a/__tests__/components/Code.test.tsx
+++ b/__tests__/components/Code.test.tsx
@@ -1,47 +1,59 @@
-import { fireEvent, render, screen } from '@testing-library/react';
-import copy from 'copy-to-clipboard';
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
import React from 'react';
-import { vi } from 'vitest';
-
import Code from '../../components/Code';
-
-
-const codeProps = {
- copyButtons: true,
-};
-
-vi.mock('@readme/syntax-highlighter', () => ({
- default: code => {
- return {code.replace(/<<.*?>>/, 'VARIABLE_SUBSTITUTED')};
- },
- canonical: lang => lang,
-}));
-
-describe.skip('Code', () => {
- it.skip('copies the variable interpolated code', () => {
- const props = {
- children: ['console.log("<>");'],
- };
-
- const { container } = render();
-
- expect(container).toHaveTextContent(/VARIABLE_SUBSTITUTED/);
- fireEvent.click(screen.getByRole('button'));
-
- expect(copy).toHaveBeenCalledWith(expect.stringMatching(/VARIABLE_SUBSTITUTED/));
+import { mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
+
+describe('Code', () => {
+ describe('mdxish', () => {
+ it('renders a fenced code block', () => {
+ const md = `\`\`\`js
+const x = 1;
+\`\`\``;
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ expect(container.querySelector('code')).toBeInTheDocument();
+ expect(container.textContent).toContain('const x = 1;');
+ });
+
+ it('renders inline code', () => {
+ const md = 'Use `console.log()` to debug';
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ expect(container.querySelector('code')).toBeInTheDocument();
+ expect(container.textContent).toContain('console.log()');
+ });
});
- it.skip('does not nest the button inside the code block', () => {
- render({'console.log("hi");'});
- const btn = screen.getByRole('button');
+ describe('mdx', () => {
+ it('renders a fenced code block', () => {
+ const md = '```js\nconst x = 1;\n```';
+ const Content = execute(md);
+ const { container } = render();
- expect(btn.parentNode?.nodeName.toLowerCase()).not.toBe('code');
+ expect(container.querySelector('code')).toBeInTheDocument();
+ expect(container.textContent).toContain('const x = 1;');
+ });
});
- it.skip('allows undefined children?!', () => {
- const { container } = render();
-
- expect(container).toHaveTextContent('');
+ describe('render', () => {
+ it('renders a code element', () => {
+ const { container } = render({'console.log("hi");'});
+ expect(container.querySelector('code.rdmd-code')).toBeInTheDocument();
+ });
+
+ it('renders children as code content', () => {
+ const { container } = render({'console.log("hi");'});
+ expect(container).toHaveTextContent('console.log("hi");');
+ });
+
+ it('handles undefined children', () => {
+ const { container } = render();
+ expect(container).toHaveTextContent('');
+ });
});
});
diff --git a/__tests__/components/CodeTabs.test.tsx b/__tests__/components/CodeTabs.test.tsx
index eb2d420e5..630025438 100644
--- a/__tests__/components/CodeTabs.test.tsx
+++ b/__tests__/components/CodeTabs.test.tsx
@@ -1,11 +1,47 @@
+import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
import React from 'react';
+import Code from '../../components/Code';
+import CodeTabs from '../../components/CodeTabs';
+import { mdxish, renderMdxish } from '../../lib';
import { execute } from '../helpers';
describe('CodeTabs', () => {
- it.skip('render _all_ its children', () => {
- const md = `
+ describe('mdxish', () => {
+ it('combines consecutive code blocks into CodeTabs', () => {
+ const md = `\`\`\`js
+const a = 1;
+\`\`\`
+\`\`\`py
+a = 1
+\`\`\``;
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ expect(container.querySelector('.CodeTabs')).toBeInTheDocument();
+ expect(container.textContent).toContain('const a = 1;');
+ expect(container.textContent).toContain('a = 1');
+ });
+
+ it('renders toolbar buttons with language names', () => {
+ const md = `\`\`\`javascript
+code1
+\`\`\`
+\`\`\`python
+code2
+\`\`\``;
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
+
+ const buttons = container.querySelectorAll('button');
+ expect(buttons).toHaveLength(2);
+ });
+ });
+
+ describe('mdx', () => {
+ it('render _all_ its children', () => {
+ const md = `
\`\`\`
assert('theme', 'dark');
\`\`\`
@@ -13,10 +49,43 @@ 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')");
+ });
+ });
+
+ describe('render', () => {
+ it('renders the CodeTabs wrapper', () => {
+ const { container } = render(
+
+ {'console.log("hello");'}
+ ,
+ );
+ expect(container.querySelector('.CodeTabs')).toBeInTheDocument();
+ });
+
+ it('renders toolbar buttons for each tab', () => {
+ const { container } = render(
+
+ {'const a = 1;'}
+ {'a = 1'}
+ ,
+ );
+ const buttons = container.querySelectorAll('.CodeTabs-toolbar button');
+ expect(buttons).toHaveLength(2);
+ });
- expect(container).toHaveTextContent("assert('theme', 'dark')");
- expect(container).toHaveTextContent("assert('theme', 'light')");
+ it('renders code content in CodeTabs-inner', () => {
+ const { container } = render(
+
+ {'const a = 1;'}
+ ,
+ );
+ expect(container.querySelector('.CodeTabs-inner')).toBeInTheDocument();
+ expect(container).toHaveTextContent('const a = 1;');
+ });
});
});
diff --git a/__tests__/components/Columns.test.tsx b/__tests__/components/Columns.test.tsx
new file mode 100644
index 000000000..a88d7e82c
--- /dev/null
+++ b/__tests__/components/Columns.test.tsx
@@ -0,0 +1,127 @@
+import type { Element } from 'hast';
+
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
+import React from 'react';
+
+import Columns, { Column } from '../../components/Columns';
+import { mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
+
+describe('Columns', () => {
+ describe('mdxish', () => {
+ describe('given Columns with two Column children', () => {
+ const md = `
+
+ Col 1
+ Col 2
+
+`;
+ const mod = renderMdxish(mdxish(md));
+
+ 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('renders Columns with Column children', () => {
+ const md = 'Col 1Col 2';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('.Columns')).toBeInTheDocument();
+ const cols = container.querySelectorAll('.Column');
+ expect(cols).toHaveLength(2);
+ expect(cols[0]).toHaveTextContent('Col 1');
+ expect(cols[1]).toHaveTextContent('Col 2');
+ });
+ });
+
+ describe('render', () => {
+ it('renders a Columns wrapper with grid style', () => {
+ const { container } = render(
+
+ A
+ B
+ ,
+ );
+ const columns = container.querySelector('.Columns');
+ expect(columns).toBeInTheDocument();
+ expect(columns).toHaveStyle({ gridTemplateColumns: 'repeat(2, auto)' });
+ });
+
+ it('renders Column children', () => {
+ const { container } = render(
+
+ Col 1
+ Col 2
+ ,
+ );
+ const cols = container.querySelectorAll('.Column');
+ expect(cols).toHaveLength(2);
+ expect(cols[0]).toHaveTextContent('Col 1');
+ expect(cols[1]).toHaveTextContent('Col 2');
+ });
+
+ it('adjusts grid columns based on child count', () => {
+ const { container } = render(
+
+ A
+ B
+ C
+ ,
+ );
+ expect(container.querySelector('.Columns')).toHaveStyle({ gridTemplateColumns: 'repeat(3, auto)' });
+ });
+ });
+});
diff --git a/__tests__/components/Embed.test.tsx b/__tests__/components/Embed.test.tsx
new file mode 100644
index 000000000..17437fcf3
--- /dev/null
+++ b/__tests__/components/Embed.test.tsx
@@ -0,0 +1,173 @@
+import type { Element } from 'hast';
+
+import '@testing-library/jest-dom';
+import { render } from '@testing-library/react';
+import React from 'react';
+
+import Embed from '../../components/Embed';
+import { mdast, mdx, mdxish, renderMdxish } from '../../lib';
+import { execute } from '../helpers';
+
+describe('Embed', () => {
+ describe('mdxish', () => {
+ describe('given an Embed in link mode', () => {
+ const md = `
+
+`;
+ const mod = renderMdxish(mdxish(md));
+
+ 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 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 an @embed link', () => {
+ it('should convert an @embed link to an embed HAST node', () => {
+ const txt = '[Embedded meta links.](https://nyti.me/s/gzoa2xb2v3 "@embed")';
+ const hast = mdxish(txt);
+ const embed = hast.children[0] as Element;
+
+ expect(embed.type).toBe('element');
+ expect(embed.tagName).toBe('embed');
+ expect(embed.properties.url).toBe('https://nyti.me/s/gzoa2xb2v3');
+ expect(embed.properties.title).toBe('Embedded meta links.');
+ });
+ });
+
+ 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('renders an embed in link mode', () => {
+ const md = '';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.querySelector('.embed')).toBeInTheDocument();
+ const link = container.querySelector('a.embed-link');
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ });
+
+ it('renders an embed in iframe mode', () => {
+ const md = '';
+ const Content = execute(md);
+ const { container } = render();
+
+ const iframe = container.querySelector('iframe');
+ expect(iframe).toBeInTheDocument();
+ expect(iframe).toHaveAttribute('src', 'https://example.com');
+ });
+
+ it('renders an embed with html content', () => {
+ const md = ``;
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.innerHTML).toMatchSnapshot();
+ });
+
+ it('renders an embed with meta content', () => {
+ const md = ``;
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.innerHTML).toMatchSnapshot();
+ });
+
+ it('renders an @embed link as an embed', () => {
+ const txt = '[Embedded meta links.](https://nyti.me/s/gzoa2xb2v3 "@embed")';
+ const ast = mdast(txt);
+ const out = mdx(ast);
+ expect(out).toMatchSnapshot();
+ });
+
+ it('renders an rdmd embed link', () => {
+ const md = '[rdmd](https://www.nytimes.com/2020/05/03/us/politics/george-w-bush-coronavirus-unity.html "@embed")';
+ const Content = execute(md);
+ const { container } = render();
+
+ expect(container.innerHTML).toMatchSnapshot();
+ });
+ });
+
+ describe('render', () => {
+ it('renders in link mode with title', () => {
+ const { container } = render();
+ expect(container.querySelector('.embed')).toBeInTheDocument();
+ const link = container.querySelector('a.embed-link');
+ expect(link).toHaveAttribute('href', 'https://example.com');
+ expect(container.querySelector('.embed-title')).toHaveTextContent('Example');
+ });
+
+ it('renders in iframe mode', () => {
+ const { container } = render();
+ const iframe = container.querySelector('iframe');
+ expect(iframe).toHaveAttribute('src', 'https://example.com');
+ expect(iframe).toHaveAttribute('title', 'Example');
+ });
+
+ it('renders html content when html prop is provided', () => {
+ const { container } = render();
+ expect(container.querySelector('.embed-media')).toBeInTheDocument();
+ });
+
+ it('derives provider from url', () => {
+ const { container } = render();
+ expect(container.querySelector('.embed-provider')).toHaveTextContent('example.com');
+ });
+ });
+});
diff --git a/__tests__/components/Glossary.test.tsx b/__tests__/components/Glossary.test.tsx
index f190f6539..0c1c35f53 100644
--- a/__tests__/components/Glossary.test.tsx
+++ b/__tests__/components/Glossary.test.tsx
@@ -1,14 +1,74 @@
-import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
+import { Glossary } from '../../components/Glossary';
+import { mdxish, renderMdxish } from '../../lib';
import { execute } from '../helpers';
describe('Glossary', () => {
- it('renders a glossary item', () => {
- const md = 'parliament';
- const Content = execute(md);
- render();
+ describe('mdxish', () => {
+ it('renders a glossary item without errors', () => {
+ const md = 'The term exogenous should render.';
+ const mod = renderMdxish(mdxish(md));
+ render();
- expect(screen.getByText('parliament')).toBeVisible();
+ expect(screen.getByText('exogenous')).toBeVisible();
+ });
+ });
+
+ describe('mdx', () => {
+ it('renders a glossary item', () => {
+ const md = 'parliament';
+ const Content = execute(md);
+ render();
+
+ expect(screen.getByText('parliament')).toBeVisible();
+ });
+ });
+
+ describe('render', () => {
+ it('renders the term when found in glossary', () => {
+ const terms = [{ term: 'API', definition: 'Application Programming Interface' }];
+ render(API);
+ expect(screen.getByText('API')).toBeInTheDocument();
+ });
+
+ it('renders a tooltip trigger when term is found', () => {
+ const terms = [{ term: 'API', definition: 'Application Programming Interface' }];
+ const { container } = render(API);
+ expect(container.querySelector('.GlossaryItem-trigger')).toBeInTheDocument();
+ });
+
+ it('renders a plain span when term is not found', () => {
+ const terms = [{ term: 'API', definition: 'Application Programming Interface' }];
+ const { container } = render(unknown);
+ expect(container.querySelector('.GlossaryItem-trigger')).not.toBeInTheDocument();
+ expect(screen.getByText('unknown')).toBeInTheDocument();
+ });
+
+ it('shows tooltip with definition on hover', () => {
+ const terms = [{ term: 'acme', definition: 'This is a definition' }];
+ const { container } = render(acme);
+
+ const trigger = container.querySelector('.GlossaryItem-trigger');
+ expect(trigger).toHaveTextContent('acme');
+ fireEvent.mouseEnter(trigger!);
+ expect(screen.getByText('This is a definition', { exact: false })).toHaveTextContent(
+ 'acme - This is a definition',
+ );
+ });
+
+ it('matches terms case-insensitively and shows tooltip', () => {
+ const terms = [{ term: 'aCme', definition: 'This is a definition' }];
+ const { container } = render(acme);
+
+ const trigger = container.querySelector('.GlossaryItem-trigger');
+ expect(trigger).toHaveTextContent('acme');
+ fireEvent.mouseEnter(trigger!);
+ expect(screen.getByText('This is a definition', { exact: false })).toHaveTextContent(
+ 'aCme - This is a definition',
+ );
+ });
});
});
diff --git a/__tests__/components/HTMLBlock.test.tsx b/__tests__/components/HTMLBlock.test.tsx
index b080762e8..5a982d448 100644
--- a/__tests__/components/HTMLBlock.test.tsx
+++ b/__tests__/components/HTMLBlock.test.tsx
@@ -1,3 +1,4 @@
+import '@testing-library/jest-dom';
import { render, screen, cleanup } from '@testing-library/react';
import React from 'react';
import { renderToStaticMarkup, renderToString } from 'react-dom/server';
@@ -5,60 +6,86 @@ import { renderToStaticMarkup, renderToString } from 'react-dom/server';
import { vi } from 'vitest';
import HTMLBlock from '../../components/HTMLBlock';
+import { mdxish, renderMdxish } from '../../lib';
import { execute } from '../helpers';
describe('HTML Block', () => {
- beforeEach(() => {
- global.mockFn = vi.fn();
- });
+ describe('mdxish', () => {
+ it('renders an HTML block from markdown', () => {
+ const md = ``;
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
- afterEach(() => {
- cleanup();
- vi.restoreAllMocks();
- });
+ expect(container.querySelector('.custom-block')).toBeInTheDocument();
+ expect(container.textContent).toContain('Hello from HTML');
+ });
- it('runs user scripts in compat mode', () => {
- render({''});
- expect(global.mockFn).toHaveBeenCalledTimes(1);
- });
+ it('renders a simple HTML element', () => {
+ const md = '
';
+ const mod = renderMdxish(mdxish(md));
+ const { container } = render();
- it("doesn't run user scripts by default", () => {
- render({''});
- expect(global.mockFn).toHaveBeenCalledTimes(0);
+ expect(container.querySelector('hr')).toBeInTheDocument();
+ });
});
- it("doesn't render user scripts by default", () => {
- render({''});
- expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
+ describe('mdx', () => {
+ it('renders the html in a `` tag if safeMode={true}', () => {
+ const md = '{``}';
+ const Component = execute(md);
+ expect(renderToStaticMarkup()).toMatchInlineSnapshot(
+ '"<button onload="alert('gotcha!')"/>
"',
+ );
+ });
});
- it("doesn't render user scripts with weird endings", () => {
- render({""});
- expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
- });
+ describe('render', () => {
+ beforeEach(() => {
+ global.mockFn = vi.fn();
+ });
- it("doesn't render user scripts with a malicious string", () => {
- render({'t>mockFn()cript>'});
- expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
- });
+ afterEach(() => {
+ cleanup();
+ vi.restoreAllMocks();
+ });
+
+ it('runs user scripts in compat mode', () => {
+ render({''});
+ expect(global.mockFn).toHaveBeenCalledTimes(1);
+ });
- it("doesn't run scripts on the server (even in compat mode)", () => {
- const html = `
+ it("doesn't run user scripts by default", () => {
+ render({''});
+ expect(global.mockFn).toHaveBeenCalledTimes(0);
+ });
+
+ it("doesn't render user scripts by default", () => {
+ render({''});
+ expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
+ });
+
+ it("doesn't render user scripts with weird endings", () => {
+ render({""});
+ expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
+ });
+
+ it("doesn't render user scripts with a malicious string", () => {
+ render({'t>mockFn()cript>'});
+ expect(screen.queryByText('mockFn()')).not.toBeInTheDocument();
+ });
+
+ it("doesn't run scripts on the server (even in compat mode)", () => {
+ const html = `
Hello World
`;
- const elem = {html};
- const view = renderToString(elem);
- expect(elem.props.runScripts).toBe(true);
- expect(view.indexOf('