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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  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-directive/consts.js';
  6. import { directiveFromMarkdown, directiveToMarkdown } from '../src/mdast-util-growi-directive/index.js';
  7. import { directive } from '../src/micromark-extension-growi-directive/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. const hoge = removePosition(
  118. fromMarkdown('x $a(#b.c.d e=f g="h&i&unknown;j")', {
  119. extensions: [directive()],
  120. mdastExtensions: [directiveFromMarkdown],
  121. }),
  122. true,
  123. );
  124. t.deepEqual(
  125. removePosition(
  126. fromMarkdown('x $a(#b.c.d e=f g="h&i&unknown;j")', {
  127. extensions: [directive()],
  128. mdastExtensions: [directiveFromMarkdown],
  129. }),
  130. true,
  131. ),
  132. {
  133. type: 'root',
  134. children: [
  135. {
  136. type: 'paragraph',
  137. children: [
  138. { type: 'text', value: 'x ' },
  139. {
  140. type: DirectiveType.Text,
  141. name: 'a',
  142. attributes: {
  143. '#b.c.d': '', e: 'f', g: 'h&i&unknown;j',
  144. },
  145. children: [],
  146. },
  147. ],
  148. },
  149. ],
  150. },
  151. 'should support attributes',
  152. );
  153. t.deepEqual(
  154. removePosition(
  155. fromMarkdown('$a(b\nc="d\ne")', {
  156. extensions: [directive()],
  157. mdastExtensions: [directiveFromMarkdown],
  158. }),
  159. true,
  160. ),
  161. {
  162. type: 'root',
  163. children: [
  164. {
  165. type: 'paragraph',
  166. children: [
  167. {
  168. type: DirectiveType.Text,
  169. name: 'a',
  170. attributes: { b: '', c: 'd\ne' },
  171. children: [],
  172. },
  173. ],
  174. },
  175. ],
  176. },
  177. 'should support EOLs in attributes',
  178. );
  179. t.end();
  180. });
  181. test('mdast -> markdown', (t) => {
  182. t.deepEqual(
  183. toMarkdown(
  184. {
  185. type: 'paragraph',
  186. children: [
  187. { type: 'text', value: 'a ' },
  188. // @ts-expect-error: `children`, `name` missing.
  189. { type: DirectiveType.Text },
  190. { type: 'text', value: ' b.' },
  191. ],
  192. },
  193. { extensions: [directiveToMarkdown] },
  194. ),
  195. 'a $ b.\n',
  196. 'should try to serialize a directive (text) w/o `name`',
  197. );
  198. t.deepEqual(
  199. toMarkdown(
  200. {
  201. type: 'paragraph',
  202. children: [
  203. { type: 'text', value: 'a ' },
  204. // @ts-expect-error: `children` missing.
  205. { type: DirectiveType.Text, name: 'b' },
  206. { type: 'text', value: ' c.' },
  207. ],
  208. },
  209. { extensions: [directiveToMarkdown] },
  210. ),
  211. 'a $b c.\n',
  212. 'should serialize a directive (text) w/ `name`',
  213. );
  214. t.deepEqual(
  215. toMarkdown(
  216. {
  217. type: 'paragraph',
  218. children: [
  219. { type: 'text', value: 'a ' },
  220. {
  221. type: DirectiveType.Text,
  222. name: 'b',
  223. children: [{ type: 'text', value: 'c' }],
  224. },
  225. { type: 'text', value: ' d.' },
  226. ],
  227. },
  228. { extensions: [directiveToMarkdown] },
  229. ),
  230. 'a $b[c] d.\n',
  231. 'should serialize a directive (text) w/ `children`',
  232. );
  233. t.deepEqual(
  234. toMarkdown(
  235. {
  236. type: 'paragraph',
  237. children: [
  238. { type: 'text', value: 'a ' },
  239. {
  240. type: DirectiveType.Text,
  241. name: 'b',
  242. children: [{ type: 'text', value: 'c[d]e' }],
  243. },
  244. { type: 'text', value: ' f.' },
  245. ],
  246. },
  247. { extensions: [directiveToMarkdown] },
  248. ),
  249. 'a $b[c\\[d\\]e] f.\n',
  250. 'should escape brackets in a directive (text) label',
  251. );
  252. t.deepEqual(
  253. toMarkdown(
  254. {
  255. type: 'paragraph',
  256. children: [
  257. { type: 'text', value: 'a ' },
  258. {
  259. type: DirectiveType.Text,
  260. name: 'b',
  261. children: [{ type: 'text', value: 'c\nd' }],
  262. },
  263. { type: 'text', value: ' e.' },
  264. ],
  265. },
  266. { extensions: [directiveToMarkdown] },
  267. ),
  268. 'a $b[c\nd] e.\n',
  269. 'should support EOLs in a directive (text) label',
  270. );
  271. t.deepEqual(
  272. toMarkdown(
  273. {
  274. type: 'paragraph',
  275. children: [
  276. { type: 'text', value: 'a ' },
  277. {
  278. type: DirectiveType.Text,
  279. name: 'b',
  280. // @ts-expect-error: should contain only `string`s
  281. attributes: {
  282. c: 'd', e: 'f', g: '', h: null, i: undefined, j: 2,
  283. },
  284. children: [],
  285. },
  286. { type: 'text', value: ' k.' },
  287. ],
  288. },
  289. { extensions: [directiveToMarkdown] },
  290. ),
  291. 'a $b(c="d" e="f" g j="2") k.\n',
  292. 'should serialize a directive (text) w/ `attributes`',
  293. );
  294. t.deepEqual(
  295. toMarkdown(
  296. {
  297. type: 'paragraph',
  298. children: [
  299. { type: 'text', value: 'a ' },
  300. {
  301. type: DirectiveType.Text,
  302. name: 'b',
  303. attributes: { '#d': '', '.a.b.c': '', key: 'value' },
  304. children: [],
  305. },
  306. { type: 'text', value: ' k.' },
  307. ],
  308. },
  309. { extensions: [directiveToMarkdown] },
  310. ),
  311. 'a $b(#d .a.b.c key="value") k.\n',
  312. 'should serialize a directive (text) w/ hash, dot notation attributes',
  313. );
  314. t.deepEqual(
  315. toMarkdown(
  316. {
  317. type: 'paragraph',
  318. children: [
  319. { type: 'text', value: 'a ' },
  320. {
  321. type: DirectiveType.Text,
  322. name: 'b',
  323. attributes: { x: 'y"\'\r\nz' },
  324. children: [],
  325. },
  326. { type: 'text', value: ' k.' },
  327. ],
  328. },
  329. { extensions: [directiveToMarkdown] },
  330. ),
  331. 'a $b(x="y"\'\r\nz") k.\n',
  332. 'should encode the quote in an attribute value (text)',
  333. );
  334. t.deepEqual(
  335. toMarkdown(
  336. {
  337. type: 'paragraph',
  338. children: [
  339. { type: 'text', value: 'a ' },
  340. {
  341. type: DirectiveType.Text,
  342. name: 'b',
  343. attributes: { x: 'y"\'\r\nz' },
  344. children: [],
  345. },
  346. { type: 'text', value: ' k.' },
  347. ],
  348. },
  349. { extensions: [directiveToMarkdown] },
  350. ),
  351. 'a $b(x="y"\'\r\nz") k.\n',
  352. 'should encode the quote in an attribute value (text)',
  353. );
  354. t.deepEqual(
  355. toMarkdown(
  356. {
  357. type: 'paragraph',
  358. children: [
  359. { type: 'text', value: 'a ' },
  360. {
  361. type: DirectiveType.Text,
  362. name: 'b',
  363. attributes: { id: 'c#d' },
  364. children: [],
  365. },
  366. { type: 'text', value: ' e.' },
  367. ],
  368. },
  369. { extensions: [directiveToMarkdown] },
  370. ),
  371. 'a $b(id="c#d") e.\n',
  372. 'should not use the `id` shortcut if impossible characters exist',
  373. );
  374. t.deepEqual(
  375. toMarkdown(
  376. {
  377. type: 'paragraph',
  378. children: [
  379. { type: 'text', value: 'a ' },
  380. {
  381. type: DirectiveType.Text,
  382. name: 'b',
  383. attributes: { 'c.d': '', 'e<f': '' },
  384. children: [],
  385. },
  386. { type: 'text', value: ' g.' },
  387. ],
  388. },
  389. { extensions: [directiveToMarkdown] },
  390. ),
  391. 'a $b(c.d e<f) g.\n',
  392. 'should not use the `class` shortcut if impossible characters exist',
  393. );
  394. t.deepEqual(
  395. toMarkdown(
  396. {
  397. type: 'paragraph',
  398. children: [
  399. { type: 'text', value: 'a ' },
  400. {
  401. type: DirectiveType.Text,
  402. name: 'b',
  403. attributes: {
  404. 'c.d': '', e: '', 'f<g': '', hij: '',
  405. },
  406. children: [],
  407. },
  408. { type: 'text', value: ' k.' },
  409. ],
  410. },
  411. { extensions: [directiveToMarkdown] },
  412. ),
  413. 'a $b(c.d e f<g hij) k.\n',
  414. 'should not use the `class` shortcut if impossible characters exist (but should use it for classes that don’t)',
  415. );
  416. t.deepEqual(
  417. // @ts-expect-error: `children`, `name` missing.
  418. toMarkdown({ type: DirectiveType.Leaf }, { extensions: [directiveToMarkdown] }),
  419. '$\n',
  420. 'should try to serialize a directive (leaf) w/o `name`',
  421. );
  422. t.deepEqual(
  423. toMarkdown(
  424. // @ts-expect-error: `children` missing.
  425. { type: DirectiveType.Leaf, name: 'a' },
  426. { extensions: [directiveToMarkdown] },
  427. ),
  428. '$a\n',
  429. 'should serialize a directive (leaf) w/ `name`',
  430. );
  431. t.deepEqual(
  432. toMarkdown(
  433. {
  434. type: DirectiveType.Leaf,
  435. name: 'a',
  436. children: [{ type: 'text', value: 'b' }],
  437. },
  438. { extensions: [directiveToMarkdown] },
  439. ),
  440. '$a[b]\n',
  441. 'should serialize a directive (leaf) w/ `children`',
  442. );
  443. t.deepEqual(
  444. toMarkdown(
  445. {
  446. type: DirectiveType.Leaf,
  447. name: 'a',
  448. children: [{ type: 'text', value: 'b' }],
  449. },
  450. { extensions: [directiveToMarkdown] },
  451. ),
  452. '$a[b]\n',
  453. 'should serialize a directive (leaf) w/ `children`',
  454. );
  455. t.deepEqual(
  456. toMarkdown(
  457. {
  458. type: DirectiveType.Leaf,
  459. name: 'a',
  460. children: [{ type: 'text', value: 'b\nc' }],
  461. },
  462. { extensions: [directiveToMarkdown] },
  463. ),
  464. '$a[b&#xA;c]\n',
  465. 'should serialize a directive (leaf) w/ EOLs in `children`',
  466. );
  467. t.deepEqual(
  468. toMarkdown(
  469. {
  470. type: DirectiveType.Leaf,
  471. name: 'a',
  472. attributes: { '#b': '', '.c.d': '', key: 'e\nf' },
  473. children: [],
  474. },
  475. { extensions: [directiveToMarkdown] },
  476. ),
  477. '$a(#b .c.d key="e&#xA;f")\n',
  478. 'should serialize a directive (leaf) w/ EOLs in `attributes`',
  479. );
  480. t.deepEqual(
  481. toMarkdown(
  482. {
  483. type: 'paragraph',
  484. children: [{ type: 'text', value: 'a$b' }],
  485. },
  486. { extensions: [directiveToMarkdown] },
  487. ),
  488. 'a\\$b\n',
  489. 'should escape a `:` in phrasing when followed by an alpha',
  490. );
  491. t.deepEqual(
  492. toMarkdown(
  493. {
  494. type: 'paragraph',
  495. children: [{ type: 'text', value: 'a$9' }],
  496. },
  497. { extensions: [directiveToMarkdown] },
  498. ),
  499. 'a$9\n',
  500. 'should not escape a `:` in phrasing when followed by a non-alpha',
  501. );
  502. t.deepEqual(
  503. toMarkdown(
  504. {
  505. type: 'paragraph',
  506. children: [{ type: 'text', value: 'a$c' }],
  507. },
  508. { extensions: [directiveToMarkdown] },
  509. ),
  510. 'a\\$c\n',
  511. 'should not escape a `:` in phrasing when preceded by a colon',
  512. );
  513. t.deepEqual(
  514. toMarkdown(
  515. {
  516. type: 'paragraph',
  517. children: [{ type: 'text', value: '$\na' }],
  518. },
  519. { extensions: [directiveToMarkdown] },
  520. ),
  521. '$\na\n',
  522. 'should not escape a `:` at a break',
  523. );
  524. t.deepEqual(
  525. toMarkdown(
  526. {
  527. type: 'paragraph',
  528. children: [{ type: 'text', value: '$a' }],
  529. },
  530. { extensions: [directiveToMarkdown] },
  531. ),
  532. '\\$a\n',
  533. 'should not escape a `:` at a break when followed by an alpha',
  534. );
  535. t.deepEqual(
  536. toMarkdown(
  537. {
  538. type: 'paragraph',
  539. children: [{ type: 'text', value: '$\na' }],
  540. },
  541. { extensions: [directiveToMarkdown] },
  542. ),
  543. '$\na\n',
  544. 'should escape a `:` at a break when followed by a colon',
  545. );
  546. t.deepEqual(
  547. toMarkdown(
  548. {
  549. type: 'paragraph',
  550. children: [
  551. { type: DirectiveType.Text, name: 'red', children: [] },
  552. { type: 'text', value: '$' },
  553. ],
  554. },
  555. { extensions: [directiveToMarkdown] },
  556. ),
  557. '$red$\n',
  558. 'should escape a `:` after a text directive',
  559. );
  560. t.end();
  561. });