mdast-util-growi-plugin.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. import { fromMarkdown } from 'mdast-util-from-markdown';
  2. import { toMarkdown } from 'mdast-util-to-markdown';
  3. import test from 'tape';
  4. import { removePosition } from 'unist-util-remove-position';
  5. import { DirectiveType } from '../src/mdast-util-growi-plugin/consts.js';
  6. import { directiveFromMarkdown, directiveToMarkdown } from '../src/mdast-util-growi-plugin/index.js';
  7. import { directive } from '../src/micromark-extension-growi-plugin/index.js';
  8. test('markdown -> mdast', (t) => {
  9. t.deepEqual(
  10. fromMarkdown('a $b[c](d) e.', {
  11. extensions: [directive()],
  12. mdastExtensions: [directiveFromMarkdown],
  13. }).children[0],
  14. {
  15. type: 'paragraph',
  16. children: [
  17. {
  18. type: 'text',
  19. value: 'a ',
  20. position: {
  21. start: { line: 1, column: 1, offset: 0 },
  22. end: { line: 1, column: 3, offset: 2 },
  23. },
  24. },
  25. {
  26. type: DirectiveType.Text,
  27. name: 'b',
  28. attributes: { d: '' },
  29. children: [
  30. {
  31. type: 'text',
  32. value: 'c',
  33. position: {
  34. start: { line: 1, column: 6, offset: 5 },
  35. end: { line: 1, column: 7, offset: 6 },
  36. },
  37. },
  38. ],
  39. position: {
  40. start: { line: 1, column: 3, offset: 2 },
  41. end: { line: 1, column: 11, offset: 10 },
  42. },
  43. },
  44. {
  45. type: 'text',
  46. value: ' e.',
  47. position: {
  48. start: { line: 1, column: 11, offset: 10 },
  49. end: { line: 1, column: 14, offset: 13 },
  50. },
  51. },
  52. ],
  53. position: {
  54. start: { line: 1, column: 1, offset: 0 },
  55. end: { line: 1, column: 14, offset: 13 },
  56. },
  57. },
  58. 'should support directives (text)',
  59. );
  60. t.deepEqual(
  61. fromMarkdown('$a[b](c)', {
  62. extensions: [directive()],
  63. mdastExtensions: [directiveFromMarkdown],
  64. }).children[0],
  65. {
  66. type: DirectiveType.Leaf,
  67. name: 'a',
  68. attributes: { c: '' },
  69. children: [
  70. {
  71. type: 'text',
  72. value: 'b',
  73. position: {
  74. start: { line: 1, column: 4, offset: 3 },
  75. end: { line: 1, column: 5, offset: 4 },
  76. },
  77. },
  78. ],
  79. position: {
  80. start: { line: 1, column: 1, offset: 0 },
  81. end: { line: 1, column: 9, offset: 8 },
  82. },
  83. },
  84. 'should support directives (leaf)',
  85. );
  86. t.deepEqual(
  87. removePosition(
  88. fromMarkdown('x $a[b *c*\nd]', {
  89. extensions: [directive()],
  90. mdastExtensions: [directiveFromMarkdown],
  91. }),
  92. true,
  93. ),
  94. {
  95. type: 'root',
  96. children: [
  97. {
  98. type: 'paragraph',
  99. children: [
  100. { type: 'text', value: 'x ' },
  101. {
  102. type: DirectiveType.Text,
  103. name: 'a',
  104. attributes: {},
  105. children: [
  106. { type: 'text', value: 'b ' },
  107. { type: 'emphasis', children: [{ type: 'text', value: 'c' }] },
  108. { type: 'text', value: '\nd' },
  109. ],
  110. },
  111. ],
  112. },
  113. ],
  114. },
  115. 'should support content in a label',
  116. );
  117. t.deepEqual(
  118. removePosition(
  119. fromMarkdown('x $a(#b.c.d e=f g="h&i&unknown;j")', {
  120. extensions: [directive()],
  121. mdastExtensions: [directiveFromMarkdown],
  122. }),
  123. true,
  124. ),
  125. {
  126. type: 'root',
  127. children: [
  128. {
  129. type: 'paragraph',
  130. children: [
  131. { type: 'text', value: 'x ' },
  132. {
  133. type: DirectiveType.Text,
  134. name: 'a',
  135. attributes: {
  136. id: 'b', class: 'c d', e: 'f', g: 'h&i&unknown;j',
  137. },
  138. children: [],
  139. },
  140. ],
  141. },
  142. ],
  143. },
  144. 'should support attributes',
  145. );
  146. t.deepEqual(
  147. removePosition(
  148. fromMarkdown('$a(b\nc="d\ne")', {
  149. extensions: [directive()],
  150. mdastExtensions: [directiveFromMarkdown],
  151. }),
  152. true,
  153. ),
  154. {
  155. type: 'root',
  156. children: [
  157. {
  158. type: 'paragraph',
  159. children: [
  160. {
  161. type: DirectiveType.Text,
  162. name: 'a',
  163. attributes: { b: '', c: 'd\ne' },
  164. children: [],
  165. },
  166. ],
  167. },
  168. ],
  169. },
  170. 'should support EOLs in attributes',
  171. );
  172. t.end();
  173. });
  174. test('mdast -> markdown', (t) => {
  175. t.deepEqual(
  176. toMarkdown(
  177. {
  178. type: 'paragraph',
  179. children: [
  180. { type: 'text', value: 'a ' },
  181. // @ts-expect-error: `children`, `name` missing.
  182. { type: DirectiveType.Text },
  183. { type: 'text', value: ' b.' },
  184. ],
  185. },
  186. { extensions: [directiveToMarkdown] },
  187. ),
  188. 'a $ b.\n',
  189. 'should try to serialize a directive (text) w/o `name`',
  190. );
  191. t.deepEqual(
  192. toMarkdown(
  193. {
  194. type: 'paragraph',
  195. children: [
  196. { type: 'text', value: 'a ' },
  197. // @ts-expect-error: `children` missing.
  198. { type: DirectiveType.Text, name: 'b' },
  199. { type: 'text', value: ' c.' },
  200. ],
  201. },
  202. { extensions: [directiveToMarkdown] },
  203. ),
  204. 'a $b c.\n',
  205. 'should serialize a directive (text) w/ `name`',
  206. );
  207. t.deepEqual(
  208. toMarkdown(
  209. {
  210. type: 'paragraph',
  211. children: [
  212. { type: 'text', value: 'a ' },
  213. {
  214. type: DirectiveType.Text,
  215. name: 'b',
  216. children: [{ type: 'text', value: 'c' }],
  217. },
  218. { type: 'text', value: ' d.' },
  219. ],
  220. },
  221. { extensions: [directiveToMarkdown] },
  222. ),
  223. 'a $b[c] d.\n',
  224. 'should serialize a directive (text) w/ `children`',
  225. );
  226. t.deepEqual(
  227. toMarkdown(
  228. {
  229. type: 'paragraph',
  230. children: [
  231. { type: 'text', value: 'a ' },
  232. {
  233. type: DirectiveType.Text,
  234. name: 'b',
  235. children: [{ type: 'text', value: 'c[d]e' }],
  236. },
  237. { type: 'text', value: ' f.' },
  238. ],
  239. },
  240. { extensions: [directiveToMarkdown] },
  241. ),
  242. 'a $b[c\\[d\\]e] f.\n',
  243. 'should escape brackets in a directive (text) label',
  244. );
  245. t.deepEqual(
  246. toMarkdown(
  247. {
  248. type: 'paragraph',
  249. children: [
  250. { type: 'text', value: 'a ' },
  251. {
  252. type: DirectiveType.Text,
  253. name: 'b',
  254. children: [{ type: 'text', value: 'c\nd' }],
  255. },
  256. { type: 'text', value: ' e.' },
  257. ],
  258. },
  259. { extensions: [directiveToMarkdown] },
  260. ),
  261. 'a $b[c\nd] e.\n',
  262. 'should support EOLs in a directive (text) label',
  263. );
  264. t.deepEqual(
  265. toMarkdown(
  266. {
  267. type: 'paragraph',
  268. children: [
  269. { type: 'text', value: 'a ' },
  270. {
  271. type: DirectiveType.Text,
  272. name: 'b',
  273. // @ts-expect-error: should contain only `string`s
  274. attributes: {
  275. c: 'd', e: 'f', g: '', h: null, i: undefined, j: 2,
  276. },
  277. children: [],
  278. },
  279. { type: 'text', value: ' k.' },
  280. ],
  281. },
  282. { extensions: [directiveToMarkdown] },
  283. ),
  284. 'a $b(c="d" e="f" g j="2") k.\n',
  285. 'should serialize a directive (text) w/ `attributes`',
  286. );
  287. t.deepEqual(
  288. toMarkdown(
  289. {
  290. type: 'paragraph',
  291. children: [
  292. { type: 'text', value: 'a ' },
  293. {
  294. type: DirectiveType.Text,
  295. name: 'b',
  296. attributes: { class: 'a b\nc', id: 'd', key: 'value' },
  297. children: [],
  298. },
  299. { type: 'text', value: ' k.' },
  300. ],
  301. },
  302. { extensions: [directiveToMarkdown] },
  303. ),
  304. 'a $b(#d .a.b.c key="value") k.\n',
  305. 'should serialize a directive (text) w/ `id`, `class` attributes',
  306. );
  307. t.deepEqual(
  308. toMarkdown(
  309. {
  310. type: 'paragraph',
  311. children: [
  312. { type: 'text', value: 'a ' },
  313. {
  314. type: DirectiveType.Text,
  315. name: 'b',
  316. attributes: { x: 'y"\'\r\nz' },
  317. children: [],
  318. },
  319. { type: 'text', value: ' k.' },
  320. ],
  321. },
  322. { extensions: [directiveToMarkdown] },
  323. ),
  324. 'a $b(x="y"\'\r\nz") k.\n',
  325. 'should encode the quote in an attribute value (text)',
  326. );
  327. t.deepEqual(
  328. toMarkdown(
  329. {
  330. type: 'paragraph',
  331. children: [
  332. { type: 'text', value: 'a ' },
  333. {
  334. type: DirectiveType.Text,
  335. name: 'b',
  336. attributes: { x: 'y"\'\r\nz' },
  337. children: [],
  338. },
  339. { type: 'text', value: ' k.' },
  340. ],
  341. },
  342. { extensions: [directiveToMarkdown] },
  343. ),
  344. 'a $b(x="y"\'\r\nz") k.\n',
  345. 'should encode the quote in an attribute value (text)',
  346. );
  347. t.deepEqual(
  348. toMarkdown(
  349. {
  350. type: 'paragraph',
  351. children: [
  352. { type: 'text', value: 'a ' },
  353. {
  354. type: DirectiveType.Text,
  355. name: 'b',
  356. attributes: { id: 'c#d' },
  357. children: [],
  358. },
  359. { type: 'text', value: ' e.' },
  360. ],
  361. },
  362. { extensions: [directiveToMarkdown] },
  363. ),
  364. 'a $b(id="c#d") e.\n',
  365. 'should not use the `id` shortcut if impossible characters exist',
  366. );
  367. t.deepEqual(
  368. toMarkdown(
  369. {
  370. type: 'paragraph',
  371. children: [
  372. { type: 'text', value: 'a ' },
  373. {
  374. type: DirectiveType.Text,
  375. name: 'b',
  376. attributes: { class: 'c.d e<f' },
  377. children: [],
  378. },
  379. { type: 'text', value: ' g.' },
  380. ],
  381. },
  382. { extensions: [directiveToMarkdown] },
  383. ),
  384. 'a $b(class="c.d e<f") g.\n',
  385. 'should not use the `class` shortcut if impossible characters exist',
  386. );
  387. t.deepEqual(
  388. toMarkdown(
  389. {
  390. type: 'paragraph',
  391. children: [
  392. { type: 'text', value: 'a ' },
  393. {
  394. type: DirectiveType.Text,
  395. name: 'b',
  396. attributes: { class: 'c.d e f<g hij' },
  397. children: [],
  398. },
  399. { type: 'text', value: ' k.' },
  400. ],
  401. },
  402. { extensions: [directiveToMarkdown] },
  403. ),
  404. 'a $b(.e.hij class="c.d f<g") k.\n',
  405. 'should not use the `class` shortcut if impossible characters exist (but should use it for classes that don’t)',
  406. );
  407. t.deepEqual(
  408. // @ts-expect-error: `children`, `name` missing.
  409. toMarkdown({ type: DirectiveType.Leaf }, { extensions: [directiveToMarkdown] }),
  410. '$\n',
  411. 'should try to serialize a directive (leaf) w/o `name`',
  412. );
  413. t.deepEqual(
  414. toMarkdown(
  415. // @ts-expect-error: `children` missing.
  416. { type: DirectiveType.Leaf, name: 'a' },
  417. { extensions: [directiveToMarkdown] },
  418. ),
  419. '$a\n',
  420. 'should serialize a directive (leaf) w/ `name`',
  421. );
  422. t.deepEqual(
  423. toMarkdown(
  424. {
  425. type: DirectiveType.Leaf,
  426. name: 'a',
  427. children: [{ type: 'text', value: 'b' }],
  428. },
  429. { extensions: [directiveToMarkdown] },
  430. ),
  431. '$a[b]\n',
  432. 'should serialize a directive (leaf) w/ `children`',
  433. );
  434. t.deepEqual(
  435. toMarkdown(
  436. {
  437. type: DirectiveType.Leaf,
  438. name: 'a',
  439. children: [{ type: 'text', value: 'b' }],
  440. },
  441. { extensions: [directiveToMarkdown] },
  442. ),
  443. '$a[b]\n',
  444. 'should serialize a directive (leaf) w/ `children`',
  445. );
  446. t.deepEqual(
  447. toMarkdown(
  448. {
  449. type: DirectiveType.Leaf,
  450. name: 'a',
  451. children: [{ type: 'text', value: 'b\nc' }],
  452. },
  453. { extensions: [directiveToMarkdown] },
  454. ),
  455. '$a[b&#xA;c]\n',
  456. 'should serialize a directive (leaf) w/ EOLs in `children`',
  457. );
  458. t.deepEqual(
  459. toMarkdown(
  460. {
  461. type: DirectiveType.Leaf,
  462. name: 'a',
  463. attributes: { id: 'b', class: 'c d', key: 'e\nf' },
  464. children: [],
  465. },
  466. { extensions: [directiveToMarkdown] },
  467. ),
  468. '$a(#b .c.d key="e&#xA;f")\n',
  469. 'should serialize a directive (leaf) w/ EOLs in `attributes`',
  470. );
  471. t.deepEqual(
  472. toMarkdown(
  473. {
  474. type: 'paragraph',
  475. children: [{ type: 'text', value: 'a$b' }],
  476. },
  477. { extensions: [directiveToMarkdown] },
  478. ),
  479. 'a\\$b\n',
  480. 'should escape a `:` in phrasing when followed by an alpha',
  481. );
  482. t.deepEqual(
  483. toMarkdown(
  484. {
  485. type: 'paragraph',
  486. children: [{ type: 'text', value: 'a$9' }],
  487. },
  488. { extensions: [directiveToMarkdown] },
  489. ),
  490. 'a$9\n',
  491. 'should not escape a `:` in phrasing when followed by a non-alpha',
  492. );
  493. t.deepEqual(
  494. toMarkdown(
  495. {
  496. type: 'paragraph',
  497. children: [{ type: 'text', value: 'a$c' }],
  498. },
  499. { extensions: [directiveToMarkdown] },
  500. ),
  501. 'a\\$c\n',
  502. 'should not escape a `:` in phrasing when preceded by a colon',
  503. );
  504. t.deepEqual(
  505. toMarkdown(
  506. {
  507. type: 'paragraph',
  508. children: [{ type: 'text', value: '$\na' }],
  509. },
  510. { extensions: [directiveToMarkdown] },
  511. ),
  512. '$\na\n',
  513. 'should not escape a `:` at a break',
  514. );
  515. t.deepEqual(
  516. toMarkdown(
  517. {
  518. type: 'paragraph',
  519. children: [{ type: 'text', value: '$a' }],
  520. },
  521. { extensions: [directiveToMarkdown] },
  522. ),
  523. '\\$a\n',
  524. 'should not escape a `:` at a break when followed by an alpha',
  525. );
  526. t.deepEqual(
  527. toMarkdown(
  528. {
  529. type: 'paragraph',
  530. children: [{ type: 'text', value: '$\na' }],
  531. },
  532. { extensions: [directiveToMarkdown] },
  533. ),
  534. '$\na\n',
  535. 'should escape a `:` at a break when followed by a colon',
  536. );
  537. t.deepEqual(
  538. toMarkdown(
  539. {
  540. type: 'paragraph',
  541. children: [
  542. { type: DirectiveType.Text, name: 'red', children: [] },
  543. { type: 'text', value: '$' },
  544. ],
  545. },
  546. { extensions: [directiveToMarkdown] },
  547. ),
  548. '$red$\n',
  549. 'should escape a `:` after a text directive',
  550. );
  551. t.end();
  552. });