diff --git a/package.json b/package.json
index 3fca8ab..938c009 100644
--- a/package.json
+++ b/package.json
@@ -39,29 +39,25 @@
"dependencies": {
"@types/mdast": "^4.0.0",
"hast-util-sanitize": "^5.0.0",
- "hast-util-to-html": "^9.0.0",
"mdast-util-to-hast": "^13.0.0",
"unified": "^11.0.0"
},
"devDependencies": {
"@types/hast": "^3.0.0",
- "@types/tape": "^5.0.0",
+ "@types/node": "^20.0.0",
"c8": "^8.0.0",
"commonmark.json": "^0.30.0",
- "is-hidden": "^2.0.0",
+ "hast-util-from-html": "^2.0.0",
+ "hast-util-to-html": "^9.0.0",
"prettier": "^3.0.0",
- "rehype-parse": "^9.0.0",
- "rehype-stringify": "^10.0.0",
- "remark": "^15.0.0",
"remark-cli": "^11.0.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.0",
"remark-github": "^12.0.0",
+ "remark-parse": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"remark-slug": "^7.0.0",
"remark-toc": "^9.0.0",
- "tape": "^5.0.0",
- "to-vfile": "^8.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"vfile": "^6.0.0",
diff --git a/test/fixtures/blockquote/output.md b/test/fixtures/blockquote/output.md
new file mode 100644
index 0000000..4a95075
--- /dev/null
+++ b/test/fixtures/blockquote/output.md
@@ -0,0 +1,16 @@
+
Block Quote
+
+
+-
+
code.in.a.list();
+
+
+-
+
Paragraph.
+
+-
+
Normal list
+
+
+Paragraph.
+
diff --git a/test/fixtures/code/output.md b/test/fixtures/code/output.md
new file mode 100644
index 0000000..f14ae1a
--- /dev/null
+++ b/test/fixtures/code/output.md
@@ -0,0 +1,14 @@
+Code
+alert('some JavaScript code.');
+
+foo bar baz
+
+alpha bravo charlie
+
+
+ two spaces
+ one
+ two
+ one
+ mixed.
+
diff --git a/test/fixtures/entities-named/output.md b/test/fixtures/entities-named/output.md
new file mode 100644
index 0000000..071e009
--- /dev/null
+++ b/test/fixtures/entities-named/output.md
@@ -0,0 +1,31 @@
+Entities
+Plain text:
+AT&T with entity, AT&T with numeric entity, AT&T without entity.
+Fenced code language flags:
+Something in the AT&T language
+
+Something in the AT&T language
+
+Something in the AT&T language
+
+Automatic links:
+http://at&t.com, http://at&t.com, and http://at&t.com.
+Link href
:
+With entity, numeric entity, without entity.
+Link title
:
+With entity, numeric entity, without entity.
+Image src
:
+, , .
+Image alt
:
+, , .
+Image title
:
+, , .
+Reference link
:
+Entity, Numeric entity, Literal.
+, , .
+Reference title
:
+Entity, Numeric entity, Literal.
+, , .
+Image Reference alt
:
+, , .
+Definitions:
diff --git a/test/fixtures/entities-numerical/output.md b/test/fixtures/entities-numerical/output.md
new file mode 100644
index 0000000..c4fa09e
--- /dev/null
+++ b/test/fixtures/entities-numerical/output.md
@@ -0,0 +1,31 @@
+Entities
+Plain text:
+AT&T with entity, AT&T with numeric entity, AT&T without entity.
+Fenced code language flags:
+Something in the AT&T language
+
+Something in the AT&T language
+
+Something in the AT&T language
+
+Automatic links:
+http://at&t.com, http://at&t.com, and http://at&t.com.
+Link href
:
+With entity, numeric entity, without entity.
+Link title
:
+With entity, numeric entity, without entity.
+Image src
:
+, , .
+Image alt
:
+, , .
+Image title
:
+, , .
+Reference link
:
+Entity, Numeric entity, Literal.
+, , .
+Reference title
:
+Entity, Numeric entity, Literal.
+, , .
+Image Reference alt
:
+, , .
+Definitions:
diff --git a/test/fixtures/escape-commonmark/output.md b/test/fixtures/escape-commonmark/output.md
new file mode 100644
index 0000000..934d342
--- /dev/null
+++ b/test/fixtures/escape-commonmark/output.md
@@ -0,0 +1,39 @@
+These should all get escaped:
+Backslash: \
+Backtick: `
+Asterisk: *
+Underscore: _
+Left brace: {
+Right brace: }
+Left bracket: [
+Right bracket: ]
+Left paren: (
+Right paren: )
+Greater-than: >
+Hash: #
+Period: .
+Bang: !
+Plus: +
+Minus: -
+GFM:
+Pipe: |
+Tilde: ~
+Commonmark:
+Quote: "
+Dollar: $
+Percentage: %
+Ampersand: &
+Single quote: '
+Comma: ,
+Forward slash: /
+Colon: :
+Semicolon: ;
+Less-than: <
+Equals: =
+Question mark: ?
+At-sign: @
+Caret: ^
+New line:
+only works in paragraphs.
+Two spaces:
+only works in paragraphs.
diff --git a/test/fixtures/escape/output.md b/test/fixtures/escape/output.md
new file mode 100644
index 0000000..934d342
--- /dev/null
+++ b/test/fixtures/escape/output.md
@@ -0,0 +1,39 @@
+These should all get escaped:
+Backslash: \
+Backtick: `
+Asterisk: *
+Underscore: _
+Left brace: {
+Right brace: }
+Left bracket: [
+Right bracket: ]
+Left paren: (
+Right paren: )
+Greater-than: >
+Hash: #
+Period: .
+Bang: !
+Plus: +
+Minus: -
+GFM:
+Pipe: |
+Tilde: ~
+Commonmark:
+Quote: "
+Dollar: $
+Percentage: %
+Ampersand: &
+Single quote: '
+Comma: ,
+Forward slash: /
+Colon: :
+Semicolon: ;
+Less-than: <
+Equals: =
+Question mark: ?
+At-sign: @
+Caret: ^
+New line:
+only works in paragraphs.
+Two spaces:
+only works in paragraphs.
diff --git a/test/fixtures/html-sanitize/output.md b/test/fixtures/html-sanitize/output.md
new file mode 100644
index 0000000..f59dfe9
--- /dev/null
+++ b/test/fixtures/html-sanitize/output.md
@@ -0,0 +1,3 @@
+Foo bar baz qux.
+heading
+Alpha bravo charlie.
diff --git a/test/fixtures/html/output.md b/test/fixtures/html/output.md
new file mode 100644
index 0000000..6cec140
--- /dev/null
+++ b/test/fixtures/html/output.md
@@ -0,0 +1,2 @@
+Alpha
+Foo bar baz qux.
diff --git a/test/fixtures/images/output.md b/test/fixtures/images/output.md
new file mode 100644
index 0000000..11dab25
--- /dev/null
+++ b/test/fixtures/images/output.md
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/test/fixtures/links/output.md b/test/fixtures/links/output.md
new file mode 100644
index 0000000..62e2f4a
--- /dev/null
+++ b/test/fixtures/links/output.md
@@ -0,0 +1,6 @@
+Example
+Example
+
+
+
+
diff --git a/test/fixtures/list/output.md b/test/fixtures/list/output.md
new file mode 100644
index 0000000..bd5dfdb
--- /dev/null
+++ b/test/fixtures/list/output.md
@@ -0,0 +1,36 @@
+List
+
+- One;
+- Two;
+- ~~Three~~.
+
+
+- One;
+- Two;
+
+
+
+- Four.
+- Five.
+
+
+-
+
Loose:
+
+- Alpha;
+- Bravo;
+- Charlie.
+
+
+-
+
Loose 2:
+
+- Delta;
+- Echo;
+- Foxtrot.
+
+
+
+
+And a rule.
+
diff --git a/test/fixtures/references/output.md b/test/fixtures/references/output.md
new file mode 100644
index 0000000..f074a30
--- /dev/null
+++ b/test/fixtures/references/output.md
@@ -0,0 +1,6 @@
+References
+Entities contains some serious entity tests relating to titles and links
+in definitions.
+However, the [missing], [missing][], and [missing][missing] are omitted.
+However, the ![missing], ![missing][], and ![missing][missing] are omitted.
+Same goes for [][empty] and ![][empty].
diff --git a/test/fixtures/rule/output.md b/test/fixtures/rule/output.md
new file mode 100644
index 0000000..4851e81
--- /dev/null
+++ b/test/fixtures/rule/output.md
@@ -0,0 +1,4 @@
+Horizontal Rules
+
+
+
diff --git a/test/fixtures/self-closing/output.md b/test/fixtures/self-closing/output.md
new file mode 100644
index 0000000..e6df097
--- /dev/null
+++ b/test/fixtures/self-closing/output.md
@@ -0,0 +1,4 @@
+Hello
+world
+
+
diff --git a/test/index.js b/test/index.js
index 2258722..bdebdd0 100644
--- a/test/index.js
+++ b/test/index.js
@@ -2,284 +2,294 @@
* @typedef {import('mdast').Root} Root
* @typedef {import('mdast').Paragraph} Paragraph
* @typedef {import('hast').Element} Element
- * @typedef {import('vfile').VFile} VFile
+ * @typedef {import('unified').Pluggable} Pluggable
* @typedef {import('../index.js').Options} Options
*/
-import path from 'node:path'
-import fs from 'node:fs'
-import test from 'tape'
-import {isHidden} from 'is-hidden'
+import assert from 'node:assert/strict'
+import fs from 'node:fs/promises'
+import process from 'node:process'
+import test from 'node:test'
import {commonmark} from 'commonmark.json'
-import {toVFile} from 'to-vfile'
-import {unified} from 'unified'
-import {remark} from 'remark'
-import remarkParse from 'remark-parse'
-import remarkSlug from 'remark-slug'
+import {fromHtml} from 'hast-util-from-html'
+import {toHtml} from 'hast-util-to-html'
import remarkFrontmatter from 'remark-frontmatter'
import remarkGfm from 'remark-gfm'
import remarkGithub from 'remark-github'
+import remarkParse from 'remark-parse'
+import remarkSlug from 'remark-slug'
import remarkToc from 'remark-toc'
-import rehypeParse from 'rehype-parse'
-import rehypeStringify from 'rehype-stringify'
+import {unified} from 'unified'
+import {VFile} from 'vfile'
import remarkHtml from '../index.js'
-test('remarkHtml', (t) => {
- t.doesNotThrow(() => {
- remark().use(remarkHtml).freeze()
- }, 'should not throw if not passed options')
-
- const processorDangerous1 = remark().use(remarkHtml, {sanitize: false})
-
- t.equal(
- // @ts-expect-error: unknown node.
- processorDangerous1.stringify({type: 'alpha'}),
- '',
- 'should stringify unknown nodes'
- )
-
- t.equal(
- processorDangerous1.stringify({
- // @ts-expect-error: unknown node.
- type: 'alpha',
- children: [{type: 'strong', children: [{type: 'text', value: 'bravo'}]}]
- }),
- 'bravo
',
- 'should stringify unknown nodes'
- )
-
- t.equal(
- processorDangerous1.stringify({
- // @ts-expect-error: unknown node.
- type: 'alpha',
- children: [{type: 'text', value: 'bravo'}],
- data: {
- hName: 'i',
- hProperties: {className: 'charlie'},
- hChildren: [{type: 'text', value: 'delta'}]
- }
- }),
- 'delta',
- 'should stringify unknown nodes'
- )
-
- const processorDangerous2 = remark().use(remarkHtml, {
- sanitize: false,
- handlers: {
- /** @param {Paragraph} node */
- paragraph(state, node) {
- const head = node.children[0]
-
- if (head.type === 'text') {
- head.value = 'changed'
- }
-
- /** @type {Element} */
- const result = {
- type: 'element',
- tagName: 'p',
- properties: {},
- children: state.all(node)
- }
- state.patch(node, result)
- return state.applyData(node, result)
- }
- }
+test('remarkHtml', async function (t) {
+ await t.test('should stringify unknown nodes', async function () {
+ assert.equal(
+ unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: false})
+ .stringify({type: 'alpha'}),
+ ''
+ )
})
- t.equal(
- processorDangerous2.processSync('paragraph text').toString(),
- 'changed
\n',
- 'should allow overriding handlers'
- )
-
- const processorDangerous3 = remark()
- .use(
- /** @type {import('unified').Plugin} */
- () => (ast) => {
- // @ts-expect-error: assume it exists.
- ast.children[0].children[0].data = {
- hProperties: {title: 'overwrite'}
- }
- }
+ await t.test('should stringify unknown nodes', async function () {
+ assert.equal(
+ unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: false})
+ .stringify({
+ type: 'alpha',
+ // @ts-expect-error: unknown node.
+ children: [
+ {type: 'strong', children: [{type: 'text', value: 'bravo'}]}
+ ]
+ }),
+ 'bravo
'
)
- .use(remarkHtml, {sanitize: false})
-
- t.equal(
- processorDangerous3
- .processSync('![hello](example.jpg "overwritten")')
- .toString(),
- '\n',
- 'should patch and merge attributes'
- )
+ })
- const processorDangerous4 = remark()
- .use(
- /** @type {import('unified').Plugin} */
- () => (ast) => {
- // @ts-expect-error: assume it exists.
- ast.children[0].children[0].data = {hName: 'b'}
- }
+ await t.test('should stringify unknown nodes', async function () {
+ assert.equal(
+ unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: false})
+ .stringify({
+ type: 'alpha',
+ // @ts-expect-error: unknown node.
+ children: [{type: 'text', value: 'bravo'}],
+ data: {
+ hName: 'i',
+ hProperties: {className: 'charlie'},
+ hChildren: [{type: 'text', value: 'delta'}]
+ }
+ }),
+ 'delta'
)
- .use(remarkHtml, {sanitize: false})
-
- t.equal(
- processorDangerous4.processSync('**Bold!**').toString(),
- 'Bold!
\n',
- 'should overwrite a tag-name'
- )
+ })
- const processorDangerous5 = remark()
- .use(
- /** @type {import('unified').Plugin} */
- () => (ast) => {
- // @ts-expect-error: assume it exists.
- const code = ast.children[0].children[0]
-
- code.data = {
- hChildren: [
- {
- type: 'element',
- tagName: 'span',
- properties: {className: ['token']},
- children: [{type: 'text', value: code.value}]
+ await t.test('should allow overriding handlers', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml, {
+ sanitize: false,
+ handlers: {
+ /** @param {Paragraph} node */
+ paragraph(state, node) {
+ const head = node.children[0]
+
+ if (head.type === 'text') {
+ head.value = 'changed'
+ }
+
+ /** @type {Element} */
+ const result = {
+ type: 'element',
+ tagName: 'p',
+ properties: {},
+ children: state.all(node)
+ }
+ state.patch(node, result)
+ return state.applyData(node, result)
+ }
}
- ]
- }
- }
+ })
+ .process('paragraph text')
+ ),
+ 'changed
\n'
)
- .use(remarkHtml, {sanitize: false})
-
- t.equal(
- processorDangerous5.processSync('`var`').toString(),
- 'var
\n',
- 'should overwrite content'
- )
+ })
- const processorDangerous6 = remark()
- .use(
- /** @type {import('unified').Plugin} */
- () => (ast) => {
- // @ts-expect-error: assume it exists.
- const code = ast.children[0].children[0]
-
- code.data = {
- hChildren: [
- {
- type: 'element',
- tagName: 'output',
- properties: {className: ['token']},
- children: [{type: 'text', value: code.value}]
+ await t.test('should patch and merge attributes', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(
+ /** @type {import('unified').Plugin} */
+ () => (ast) => {
+ // @ts-expect-error: assume it exists.
+ ast.children[0].children[0].data = {
+ hProperties: {title: 'overwrite'}
+ }
}
- ]
- }
- }
+ )
+ .use(remarkHtml, {sanitize: false})
+ .process('![hello](example.jpg "overwritten")')
+ ),
+ '\n'
)
- .use(remarkHtml, {sanitize: true})
-
- t.equal(
- processorDangerous6.processSync('`var`').toString(),
- 'var
\n',
- 'should not overwrite content in `sanitize` mode'
- )
+ })
- const processorDangerous7 = remark()
- .use(
- /** @type {import('unified').Plugin} */
- () => (ast) => {
- ast.children[0].data = {
- hProperties: {className: 'foo'}
- }
- }
+ await t.test('should overwrite a tag-name', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(
+ /** @type {import('unified').Plugin} */
+ () => (ast) => {
+ // @ts-expect-error: assume it exists.
+ ast.children[0].children[0].data = {hName: 'b'}
+ }
+ )
+ .use(remarkHtml, {sanitize: false})
+ .process('**Bold!**')
+ ),
+ 'Bold!
\n'
)
- .use(remarkHtml, {sanitize: false})
-
- t.equal(
- processorDangerous7.processSync('```js\nvar\n```\n').toString(),
- 'var\n
\n',
- 'should overwrite classes on code'
- )
-
- t.equal(
- remark()
- .use(remarkHtml)
- .processSync('## Hello world')
- .toString(),
- 'Hello world
\n',
- 'should be `sanitation: true` by default'
- )
-
- t.equal(
- remark()
- .use(remarkHtml, {sanitize: true})
- .processSync('## Hello world')
- .toString(),
- 'Hello world
\n',
- 'should support sanitation: true'
- )
-
- t.equal(
- remark()
- .use(remarkHtml, {sanitize: null})
- .processSync('## Hello world')
- .toString(),
- 'Hello world
\n',
- 'should support sanitation: null'
- )
+ })
- t.equal(
- remark()
- .use(remarkHtml, {sanitize: false})
- .processSync('## Hello world')
- .toString(),
- 'Hello world
\n',
- 'should support sanitation: false'
- )
+ await t.test('should overwrite content', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(
+ /** @type {import('unified').Plugin} */
+ () => (ast) => {
+ // @ts-expect-error: assume it exists.
+ const code = ast.children[0].children[0]
+
+ code.data = {
+ hChildren: [
+ {
+ type: 'element',
+ tagName: 'span',
+ properties: {className: ['token']},
+ children: [{type: 'text', value: code.value}]
+ }
+ ]
+ }
+ }
+ )
+ .use(remarkHtml, {sanitize: false})
+ .process('`var`')
+ ),
+ 'var
\n'
+ )
+ })
- t.equal(
- remark()
- .use(remarkHtml, {sanitize: {tagNames: []}})
- .processSync('## Hello world')
- .toString(),
- 'Hello world\n',
- 'should support sanitation schemas'
+ await t.test(
+ 'should not overwrite content in `sanitize` mode',
+ async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(
+ /** @type {import('unified').Plugin} */
+ () => (ast) => {
+ // @ts-expect-error: assume it exists.
+ const code = ast.children[0].children[0]
+
+ code.data = {
+ hChildren: [
+ {
+ type: 'element',
+ tagName: 'output',
+ properties: {className: ['token']},
+ children: [{type: 'text', value: code.value}]
+ }
+ ]
+ }
+ }
+ )
+ .use(remarkHtml, {sanitize: true})
+ .process('`var`')
+ ),
+ 'var
\n'
+ )
+ }
)
- t.end()
-})
-
-// Assert fixtures.
-test('Fixtures', (t) => {
- const base = path.join('test', 'fixtures')
- const files = fs.readdirSync(base)
- let index = -1
-
- while (++index < files.length) {
- const name = files[index]
-
- if (isHidden(name)) continue
+ await t.test('should overwrite classes on code', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(
+ /** @type {import('unified').Plugin} */
+ () => (ast) => {
+ ast.children[0].data = {
+ hProperties: {className: 'foo'}
+ }
+ }
+ )
+ .use(remarkHtml, {sanitize: false})
+ .process('```js\nvar\n```\n')
+ ),
+ 'var\n
\n'
+ )
+ })
- const output = String(fs.readFileSync(path.join(base, name, 'output.html')))
- const input = String(fs.readFileSync(path.join(base, name, 'input.md')))
- const file = toVFile({path: name + '.md', value: input})
- let config = {}
+ await t.test('should be `sanitation: true` by default', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml)
+ .process('## Hello world')
+ ),
+ 'Hello world
\n'
+ )
+ })
- try {
- config = JSON.parse(
- String(fs.readFileSync(path.join(base, name, 'config.json')))
- )
- } catch {}
+ await t.test('should support sanitation: true', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: true})
+ .process('## Hello world')
+ ),
+ 'Hello world
\n'
+ )
+ })
- const result = processSync(file, config)
+ await t.test('should support sanitation: null', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: null})
+ .process('## Hello world')
+ ),
+ 'Hello world
\n'
+ )
+ })
- t.equal(result, output, 'should work on `' + name + '`')
- }
+ await t.test('should support sanitation: false', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: false})
+ .process('## Hello world')
+ ),
+ 'Hello world
\n'
+ )
+ })
- t.end()
+ await t.test('should support sanitation schemas', async function () {
+ assert.equal(
+ String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: {tagNames: []}})
+ .process('## Hello world')
+ ),
+ 'Hello world\n'
+ )
+ })
})
-test('CommonMark', (t) => {
- const skip = new Set([623, 624])
+test('CommonMark', async function (t) {
+ /** @type {Set} */
+ const skip = new Set()
let start = 0
let index = -1
/** @type {string|undefined} */
@@ -293,76 +303,134 @@ test('CommonMark', (t) => {
continue
}
- if (section !== example.section) {
- section = example.section
- start = index
- }
+ await t.test(
+ index + ': ' + example.section + ' (' + (index - start + 1) + ')',
+ async function () {
+ if (section !== example.section) {
+ section = example.section
+ start = index
+ }
- const actual = unified()
- .use(remarkParse)
- .use(remarkHtml, {sanitize: false})
- .processSync(example.markdown)
- .toString()
-
- const reformat = unified()
- .use(rehypeParse, {fragment: true})
- .use(rehypeStringify)
-
- // Normalize meaningless stuff, like character references, `
` is `
`,
- // etc.
- t.equal(
- String(reformat.processSync(actual)),
- String(reformat.processSync(example.html)),
- index + ': ' + example.section + ' (' + (index - start + 1) + ')'
+ const actual = String(
+ await unified()
+ .use(remarkParse)
+ .use(remarkHtml, {sanitize: false})
+ .process(example.markdown)
+ )
+
+ // Normalize meaningless stuff, like character references, `
` is `
`,
+ // etc.
+ assert.equal(
+ String(toHtml(fromHtml(actual))),
+ String(toHtml(fromHtml(actual)))
+ )
+ }
)
}
+})
+
+test('fixtures', async function (t) {
+ const base = new URL('fixtures/', import.meta.url)
+ const files = await fs.readdir(base)
+ let index = -1
+
+ while (++index < files.length) {
+ const folder = files[index]
+
+ if (folder.startsWith('.')) continue
+
+ await t.test(folder, async function () {
+ const folderUrl = new URL(folder + '/', base)
+ const inputUrl = new URL('input.md', folderUrl)
+ const outputUrl = new URL('output.html', folderUrl)
+ const configUrl = new URL('config.json', folderUrl)
+ const input = String(await fs.readFile(inputUrl))
+ /** @type {Options | undefined} */
+ let config
+ /** @type {string} */
+ let output
+
+ try {
+ config = JSON.parse(String(await fs.readFile(configUrl)))
+ } catch {}
+
+ const actual = String(
+ await unified()
+ .use(remarkParse)
+ // @ts-expect-error: to do.
+ .use(remarkHtml, config)
+ .process(input)
+ )
+
+ try {
+ if ('UPDATE' in process.env) {
+ throw new Error('Updating…')
+ }
+
+ output = String(await fs.readFile(outputUrl))
+ } catch {
+ output = actual
+ await fs.writeFile(outputUrl, actual)
+ }
- t.end()
+ assert.equal(actual, String(output))
+ })
+ }
})
-test('Integrations', (t) => {
+test('integrations', async function (t) {
+ /** @type {Record} */
const integrationMap = {
footnotes: remarkGfm,
frontmatter: remarkFrontmatter,
gfm: remarkGfm,
github: remarkGithub,
- toc: [remarkSlug, remarkToc]
+ toc: [
+ // @ts-expect-error: legacy.
+ // To do: remove?
+ remarkSlug,
+ remarkToc
+ ]
}
- const base = path.join('test', 'integrations')
- const files = /** @type {(keyof integrationMap)[]} */ (fs.readdirSync(base))
+ const base = new URL('integrations/', import.meta.url)
+ const files = await fs.readdir(base)
let index = -1
while (++index < files.length) {
- const name = files[index]
+ const folder = files[index]
+
+ if (folder.startsWith('.')) continue
+
+ await t.test('should integrate w/ `' + folder + '`', async function () {
+ const folderUrl = new URL(folder + '/', base)
+ const inputUrl = new URL('input.md', folderUrl)
+ const outputUrl = new URL('output.html', folderUrl)
+ const input = String(await fs.readFile(inputUrl))
+
+ const actual = String(
+ await unified()
+ .use(remarkParse)
+ // @ts-expect-error: to do.
+ .use(integrationMap[folder])
+ .use(remarkHtml, {sanitize: false})
+ .process(new VFile({path: folder + '.md', value: input}))
+ )
- if (isHidden(name)) continue
+ /** @type {string} */
+ let output
- const output = String(fs.readFileSync(path.join(base, name, 'output.html')))
- const input = String(fs.readFileSync(path.join(base, name, 'input.md')))
- const file = toVFile({path: name + '.md', value: input})
- const result = remark()
- // @ts-expect-error: fine.
- .use(integrationMap[name])
- .use(remarkHtml, {sanitize: false})
- .processSync(file)
- .toString()
+ try {
+ if ('UPDATE' in process.env) {
+ throw new Error('Updating…')
+ }
- t.equal(result, output, 'should integrate w/ `' + name + '`')
- }
+ output = String(await fs.readFile(outputUrl))
+ } catch {
+ output = actual
+ await fs.writeFile(outputUrl, actual)
+ }
- t.end()
+ assert.equal(actual, String(output))
+ })
+ }
})
-
-/**
- * @param {VFile} file
- * @param {Options} [config]
- */
-function processSync(file, config) {
- return (
- remark()
- // @ts-expect-error: to do: fix.
- .use(remarkHtml, config)
- .processSync(file)
- .toString()
- )
-}