Skip to content

Commit 761a410

Browse files
committed
Major refactor of Markdown test pipeline
1 parent c21f62b commit 761a410

54 files changed

Lines changed: 2217 additions & 1115 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

astro.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import mdx from '@astrojs/mdx'
22
import preact from "@astrojs/preact"
3-
import vercelStatic from '@astrojs/vercel/static'
3+
import vercelStatic from '@astrojs/vercel'
44
import sentry from "@sentry/astro"
55
import tailwindcss from '@tailwindcss/vite'
66
import AstroPWA from '@vite-pwa/astro'

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"@nanostores/persistent": "^1.1.0",
7272
"@sentry/astro": "^10.19.0",
7373
"@sentry/browser": "^10.19.0",
74+
"@shikijs/transformers": "^3.13.0",
7475
"@tailwindcss/forms": "0.5.10",
7576
"@tailwindcss/typography": "0.5.19",
7677
"@tailwindcss/vite": "^4.1.14",

src/lib/config/markdown.ts

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,57 @@
1+
/**
2+
* Markdown Configuration
3+
*
4+
* Overrides of function names is to provide for better debugging
5+
*/
6+
17
import type { MdxOptions } from '@astrojs/mdx'
28
import type { ShikiConfig } from 'astro/'
3-
import type { Options as RemarkTocOptions } from 'remark-toc'
4-
import type { Options as RehypeAutolinkHeadingsOptions } from 'rehype-autolink-headings'
9+
// import { transformerNotationDiff } from '@shiki/transformers'
10+
11+
/** Rehype plugins */
12+
513
import { rehypeAccessibleEmojis } from 'rehype-accessible-emojis'
14+
Object.defineProperty(rehypeAccessibleEmojis, 'name', { value: 'rehypeAccessibleEmojis' })
15+
16+
import type { Options as RehypeAutolinkHeadingsOptions } from 'rehype-autolink-headings'
617
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
7-
// Use our TypeScript implementations (modern Remark API)
8-
import remarkAbbr from '../markdown/plugins/remark-abbr/index'
9-
import remarkAttr from '../markdown/plugins/remark-attr/index'
10-
import remarkAttribution from '../markdown/plugins/remark-attribution/index'
11-
import remarkReplacements from '../markdown/plugins/remark-replacements/index'
12-
// These plugins are from npm (no custom implementation)
18+
Object.defineProperty(rehypeAutolinkHeadings, 'name', { value: 'rehypeAutolinkHeadings' })
19+
20+
/** Remark plugins */
1321
import remarkBreaks from 'remark-breaks'
1422
import remarkEmoji from 'remark-emoji'
1523
import remarkLinkifyRegex from 'remark-linkify-regex'
24+
import type { Options as RemarkTocOptions } from 'remark-toc'
1625
import remarkToc from 'remark-toc'
26+
27+
// Override function names for better debugging
28+
Object.defineProperty(remarkBreaks, 'name', { value: 'remarkBreaks' })
29+
Object.defineProperty(remarkEmoji, 'name', { value: 'remarkEmoji' })
30+
Object.defineProperty(remarkLinkifyRegex, 'name', { value: 'remarkLinkifyRegex' })
31+
Object.defineProperty(remarkToc, 'name', { value: 'remarkToc' })
32+
33+
// Create a named instance of remarkLinkifyRegex for URL auto-linking
34+
const remarkLinkifyRegexUrls = remarkLinkifyRegex(/^(https?:\/\/[^\s$.?#].[^\s]*)$/i)
35+
Object.defineProperty(remarkLinkifyRegexUrls, 'name', { value: 'remarkLinkifyRegex' })
36+
37+
// Use our TypeScript implementations (modern Remark API)
38+
import remarkAbbreviations from '../markdown/plugins/remark-abbr/index'
39+
import remarkAttributes from '../markdown/plugins/remark-attr/index'
40+
import remarkAttribution from '../markdown/plugins/remark-attribution/index'
41+
import remarkReplacements from '../markdown/plugins/remark-replacements/index'
42+
1743
/** Add custom CSS classes to Markdown-generated elements in this file */
1844
import { rehypeTailwindClasses } from '../markdown/plugins/rehype-tailwind'
1945

46+
// Override function names for better debugging
47+
Object.defineProperty(remarkAbbreviations, 'name', { value: 'remarkAbbreviations' })
48+
Object.defineProperty(remarkAttributes, 'name', { value: 'remarkAttributes' })
49+
Object.defineProperty(remarkAttribution, 'name', { value: 'remarkAttribution' })
50+
Object.defineProperty(remarkReplacements, 'name', { value: 'remarkReplacements' })
51+
Object.defineProperty(rehypeTailwindClasses, 'name', { value: 'rehypeTailwindClasses' })
52+
2053
/** Configuration for remark-attr plugin */
21-
export const remarkAttrConfig = { scope: 'permissive' } as const
54+
export const remarkAttributesConfig = { scope: 'permissive' } as const
2255

2356
/** Configuration for remark-toc plugin */
2457
export const remarkTocConfig: RemarkTocOptions = { heading: 'contents' }
@@ -64,7 +97,9 @@ export const shikiConfigOptions: ShikiConfig = {
6497
wrap: true,
6598
// Add custom transformers: https://shiki.style/guide/transformers
6699
// Find common transformers: https://shiki.style/packages/transformers
67-
transformers: [],
100+
// transformers: [
101+
// transformerNotationDiff,
102+
// ],
68103
}
69104

70105
/** Configuration for remark-rehype plugin (conversion from markdown to HTML AST) */
@@ -85,21 +120,21 @@ export const markdownConfig: Partial<MdxOptions> = {
85120
shikiConfig: shikiConfigOptions,
86121
remarkPlugins: [
87122
/** Define abbreviations at bottom file, and wraps their usage in <abbr> tags */
88-
remarkAbbr,
123+
remarkAbbreviations,
89124
/**
90125
* Add HTML attributes to elements using {.class #id key=value} syntax
91126
* Supports: headings, links, images, code blocks, lists, and bracketed spans
92127
* Example: [text content]{.class #id attr=value} creates <span> with attributes
93128
*/
94-
[remarkAttr, remarkAttrConfig],
129+
[remarkAttributes, remarkAttributesConfig],
95130
/** Wrap blockquotes with attribution in semantic figure/figcaption markup */
96131
remarkAttribution,
97132
/** Add <br/> tag to single line breaks */
98133
remarkBreaks,
99134
/** Convert emoji syntax like :heart: to emoji images */
100135
remarkEmoji,
101136
/** Automatically convert URL-like text to links */
102-
remarkLinkifyRegex(/^(https?:\/\/[^\s$.?#].[^\s]*)$/i),
137+
remarkLinkifyRegexUrls,
103138
/**
104139
* Typographic replacements for arrows, fractions, and math symbols
105140
* Complements smartypants (which handles quotes, dashes, ellipsis)

src/lib/markdown/README.md

Lines changed: 107 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,25 @@ npm i @vercel/analytics
4444
import Analytics from '@vercel/analytics/astro'
4545
https://vercel.com/docs/analytics/quickstart#add-the-analytics-component-to-your-app
4646

47+
## Difference between Integration and E2E Tests
48+
49+
| Aspect | Integration | E2E |
50+
| ---- | ---- | ---- |
51+
| Test Layer | Layer 3 | Layer 4 |
52+
| Purpose | Plugin interactions | Production rendering |
53+
| Test Data | Inline markdown strings | External fixture files |
54+
| Rendering | String processing | React component rendering |
55+
| Validation | String matching | DOM queries + Axe a11y |
56+
| Accessibility | ❌ Not tested | ✅ Comprehensive (axe) |
57+
| Test Count | 10 tests | 11 tests |
58+
| Dependencies | Vitest only | Vitest + Testing Library + vitest-axe |
59+
60+
The integration tests ensure plugins work together correctly, while the e2e tests ensure the final output is accessible and semantically correct for users.
61+
4762
### ✅ Compatible Plugins:
4863

4964
Working plugins that pass all tests:
65+
5066
- remark-attribution (custom plugin - 17/17 tests passing)
5167
- remark-breaks
5268
- remark-emoji
@@ -56,46 +72,65 @@ Working plugins that pass all tests:
5672
- rehype-autolink-headings
5773
- rehype-tailwind-classes
5874

75+
Conditional elements (have checks):
76+
77+
- a (checks for .btn and .heading-anchor)
78+
- code (checks if within pre)
79+
- h2/h3/h4
80+
- blockquote (attribution check)
81+
- pre
82+
- iframe
83+
- various special cases
84+
5985
## Markdown Config Updates
6086

87+
Details/Summary elements - These HTML elements aren't being processed by remarkGfm (they need to be raw HTML)
88+
89+
Inline code - The test shows inline code is working (it has the proper classes), but the assertion was looking for just <code> instead of the full classes
90+
91+
/** Add ==highlighted== syntax */
92+
93+
1. add remark-mark plugin
94+
2. Remove skip from integration test in
95+
6196
/**
6297
* Add accessible name to section in footnotes plugin
6398
*/
64-
const markdownFootnoteBlockOpen = () =>
65-
'<hr className="footnotes-sep">\n' +
66-
'<section class="footnotes" aria-label="footnotes">\n' +
67-
'<ol class="footnotes-list">\n'
99+
const markdownFootnoteBlockOpen = () =>
100+
'<hr className="footnotes-sep">\n' +
101+
'<section class="footnotes" aria-label="footnotes">\n' +
102+
'<ol class="footnotes-list">\n'
68103

69104
* Code tabs plugin so Javascript and Typescript examples can both be show. There can only
70105
* be white space between two code blocks. Display name is set by `tabName` and can only
71106
* contain characters in [A-Za-z0-9_]. Syntax for the first line of the code block is:
72107
* ```js [group:tabName]
73108
*/
74-
// markdown-it-codetabs//
109+
// markdown-it-codetabs//
75110

76111
/** Add copy button to code blocks */
77112
// markdown-it-copy'), markdownCodeCopyConfig)
78113
/**
79114
* Options for "copy" button added to code blocks
80115
*/
81-
// const markdownCodeCopyConfig = {
82-
/** Text shown on copy button */
83-
// btnText: `Copy`,
84-
/** Text shown on copy failure */
85-
// failText: `Copy Failed`,
86-
/** Text shown on copy success */
87-
// successText: `Success!`, // 'copy success' | copy-success text
88-
/** Amount of time to show success message */
89-
// successTextDelay: 2000,
90-
/** An HTML fragment included before <button> */
91-
// extraHtmlBeforeBtn: ``,
92-
/** An HTML fragment included after <button> */
93-
// extraHtmlAfterBtn: ``,
94-
/** Whether to show code language before the copy button */
95-
// showCodeLanguage: false,
96-
/** Test to append after the copied text like a copyright notice */
97-
// attachText: ``,
98-
// }
116+
// const markdownCodeCopyConfig = {
117+
/** Text shown on copy button */
118+
// btnText: `Copy`,
119+
/** Text shown on copy failure */
120+
// failText: `Copy Failed`,
121+
/** Text shown on copy success */
122+
// successText: `Success!`, // 'copy success' | copy-success text
123+
/** Amount of time to show success message */
124+
// successTextDelay: 2000,
125+
/** An HTML fragment included before <button> */
126+
// extraHtmlBeforeBtn: ``,
127+
/** An HTML fragment included after <button> */
128+
// extraHtmlAfterBtn: ``,
129+
/** Whether to show code language before the copy button */
130+
// showCodeLanguage: false,
131+
/** Test to append after the copied text like a copyright notice */
132+
// attachText: ``,
133+
// }
99134

100135
/** Definition lists, using indented ~ for definitions under definition header */
101136
// markdown-it-deflist//
@@ -112,12 +147,12 @@ const markdownFootnoteBlockOpen = () =>
112147
/**
113148
* Mark external, absolute links with appropriate rel & target attributes
114149
*/
115-
// const markdownExternalAnchorConfig = {
116-
/** The domain that is considered an internal link */
117-
// domain: domain,
118-
/** A class name added to anchors */
119-
// class: 'external-link',
120-
// }
150+
// const markdownExternalAnchorConfig = {
151+
/** The domain that is considered an internal link */
152+
// domain: domain,
153+
/** A class name added to anchors */
154+
// class: 'external-link',
155+
// }
121156

122157
/** es, GFM footnotes are supported in Astro, and they are enabled by using the remark-gfm plugin. This plugin allows you to use the standard footnote syntax, where you define a footnote reference inline (e.g., [^1]) and the footnote content at the bottom of the document (e.g., [^1]: This is my footnote). */
123158

@@ -136,38 +171,38 @@ const markdownFootnoteBlockOpen = () =>
136171
* Options object including parse function for content generated
137172
* by mentions plugin using `@twittername` syntax.
138173
*/
139-
// const markdownMentionsConfig = {
140-
// parseURL: username => {
174+
// const markdownMentionsConfig = {
175+
// parseURL: username => {
141176
// return `https://twitter.com/@${username}`
142-
// },
143-
/** adds a target="_blank" attribute if it's true and target="_self" if it's false */
144-
// external: true,
145-
// }
177+
// },
178+
/** adds a target="_blank" attribute if it's true and target="_self" if it's false */
179+
// external: true,
180+
// }
146181

147182
/** Mermaid JavaScript based diagramming and charting tool */
148183
// @TODO: uses ES Modules, needs Jest config adjusted. See note in spec file.
149184
//// @liradb2000/markdown-it-mermaid'), markdownMermaidConfig)
150185
/**
151186
* Mermaid JavaScript based diagramming and charting tool
152187
*/
153-
/*const markdownMermaidConfig = {
154-
startOnLoad: false,
155-
securityLevel: true,
156-
theme: 'default',
157-
flowchart: {
188+
/*const markdownMermaidConfig = {
189+
startOnLoad: false,
190+
securityLevel: true,
191+
theme: 'default',
192+
flowchart: {
158193
htmlLabels: false,
159194
useMaxWidth: true,
160-
},
161-
dictionary: {
195+
},
196+
dictionary: {
162197
token: 'mermaid',
163198
graph: 'graph',
164199
sequenceDiagram: 'sequenceDiagram',
165-
},
166-
// ...or any other options
167-
}*/
168-
markdown.syntaxHighlight.excludeLangs
169-
Type: Array<string>
170-
Default: ['math']
200+
},
201+
// ...or any other options
202+
}*/
203+
markdown.syntaxHighlight.excludeLangs
204+
Type: Array<string>
205+
Default: ['math']
171206

172207
Added in: astro@5.5.0
173208
An array of languages to exclude from the default syntax highlighting specified in markdown.syntaxHighlight.type. This can be useful when using tools that create diagrams from Markdown code blocks, such as Mermaid.js and D2.
@@ -207,22 +242,22 @@ export default defineConfig({
207242
/**
208243
* TeX rendering using KaTeX for math symbols
209244
*/
210-
/*const markdownTexmathConfig = {
211-
engine: require('katex'),
212-
delimiters: 'dollars',
213-
katexOptions: { macros: { '\\RR': '\\mathbb{R}' } },
214-
}*/
215-
/*
216-
remark-math: A Remark plugin that parses LaTeX syntax within your Markdown files.
217-
rehype-katex or rehype-mathjax: Rehype plugins that convert the parsed LaTeX into rendered HTML using either KaTeX or MathJax, respectively. KaTeX is often preferred for its performance and ability to allow text selection.
218-
To implement this:
219-
Install the necessary packages.
220-
Code
245+
/*const markdownTexmathConfig = {
246+
engine: require('katex'),
247+
delimiters: 'dollars',
248+
katexOptions: { macros: { '\\RR': '\\mathbb{R}' } },
249+
}*/
250+
/*
251+
remark-math: A Remark plugin that parses LaTeX syntax within your Markdown files.
252+
rehype-katex or rehype-mathjax: Rehype plugins that convert the parsed LaTeX into rendered HTML using either KaTeX or MathJax, respectively. KaTeX is often preferred for its performance and ability to allow text selection.
253+
To implement this:
254+
Install the necessary packages.
255+
Code
221256
222257
npm install remark-math rehype-katex katex
223-
(or rehype-mathjax if you prefer MathJax).
224-
Configure Astro: In your astro.config.mjs (or astro.config.ts), add remarkMath and rehypeKatex to your Markdown configuration:
225-
*/
258+
(or rehype-mathjax if you prefer MathJax).
259+
Configure Astro: In your astro.config.mjs (or astro.config.ts), add remarkMath and rehypeKatex to your Markdown configuration:
260+
*/
226261

227262
/** Adds underline to markdown like _underline_ */
228263
// @TODO: conflicts with built-in markup for italics: _italics_ _underline_, change one
@@ -310,12 +345,20 @@ export function myAccessibleListPlugin() {
310345

311346
┌─────────────────────────────────────────────────┐
312347
│ Layer 1: Isolated Plugin Unit Tests │
348+
│ • NPM PACKAGES ONLY (upstream regression tests)│
313349
│ • Test ONE plugin at a time │
314350
│ • Minimal pipeline (no GFM, no Astro settings) │
315-
│ • Purpose: Verify plugin logic works │
351+
│ • Purpose: Catch breaking changes from upgrades│
352+
│ • Location: __tests__/isolated_unit_tests/
316353
│ • Speed: Milliseconds │
317354
│ • Run: On every save │
318-
│ • Example: remark-attribution.spec.ts
355+
│ • Example: remark-emoji.spec.ts
356+
│ │
357+
│ Custom Plugins Tested in Plugin Directories:
358+
│ • remark-abbr → plugins/remark-abbr/__tests__ │
359+
│ • remark-attr → plugins/remark-attr/__tests__ │
360+
│ • remark-attribution → plugins/remark-attr...
361+
│ • rehype-tailwind → plugins/rehype-tailwind...
319362
└─────────────────────────────────────────────────┘
320363
321364
┌─────────────────────────────────────────────────┐

0 commit comments

Comments
 (0)