@knpkv/confluence-to-markdown 0.2.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +21 -0
  3. package/README.md +282 -14
  4. package/dist/ConfluenceAuth.d.ts +76 -0
  5. package/dist/ConfluenceAuth.d.ts.map +1 -0
  6. package/dist/ConfluenceAuth.js +366 -0
  7. package/dist/ConfluenceAuth.js.map +1 -0
  8. package/dist/ConfluenceClient.d.ts +26 -12
  9. package/dist/ConfluenceClient.d.ts.map +1 -1
  10. package/dist/ConfluenceClient.js +139 -97
  11. package/dist/ConfluenceClient.js.map +1 -1
  12. package/dist/ConfluenceConfig.d.ts +4 -24
  13. package/dist/ConfluenceConfig.d.ts.map +1 -1
  14. package/dist/ConfluenceConfig.js +45 -7
  15. package/dist/ConfluenceConfig.js.map +1 -1
  16. package/dist/ConfluenceError.d.ts +99 -16
  17. package/dist/ConfluenceError.d.ts.map +1 -1
  18. package/dist/ConfluenceError.js +88 -5
  19. package/dist/ConfluenceError.js.map +1 -1
  20. package/dist/GitError.d.ts +103 -0
  21. package/dist/GitError.d.ts.map +1 -0
  22. package/dist/GitError.js +85 -0
  23. package/dist/GitError.js.map +1 -0
  24. package/dist/GitService.d.ts +175 -0
  25. package/dist/GitService.d.ts.map +1 -0
  26. package/dist/GitService.js +431 -0
  27. package/dist/GitService.js.map +1 -0
  28. package/dist/LocalFileSystem.d.ts +29 -4
  29. package/dist/LocalFileSystem.d.ts.map +1 -1
  30. package/dist/LocalFileSystem.js +80 -6
  31. package/dist/LocalFileSystem.js.map +1 -1
  32. package/dist/MarkdownConverter.d.ts +49 -2
  33. package/dist/MarkdownConverter.d.ts.map +1 -1
  34. package/dist/MarkdownConverter.js +73 -111
  35. package/dist/MarkdownConverter.js.map +1 -1
  36. package/dist/SchemaConverterError.d.ts +108 -0
  37. package/dist/SchemaConverterError.d.ts.map +1 -0
  38. package/dist/SchemaConverterError.js +84 -0
  39. package/dist/SchemaConverterError.js.map +1 -0
  40. package/dist/Schemas.d.ts +225 -1
  41. package/dist/Schemas.d.ts.map +1 -1
  42. package/dist/Schemas.js +155 -6
  43. package/dist/Schemas.js.map +1 -1
  44. package/dist/SyncEngine.d.ts +30 -20
  45. package/dist/SyncEngine.d.ts.map +1 -1
  46. package/dist/SyncEngine.js +566 -117
  47. package/dist/SyncEngine.js.map +1 -1
  48. package/dist/ast/BlockNode.d.ts +468 -0
  49. package/dist/ast/BlockNode.d.ts.map +1 -0
  50. package/dist/ast/BlockNode.js +319 -0
  51. package/dist/ast/BlockNode.js.map +1 -0
  52. package/dist/ast/Document.d.ts +244 -0
  53. package/dist/ast/Document.d.ts.map +1 -0
  54. package/dist/ast/Document.js +69 -0
  55. package/dist/ast/Document.js.map +1 -0
  56. package/dist/ast/InlineNode.d.ts +477 -0
  57. package/dist/ast/InlineNode.d.ts.map +1 -0
  58. package/dist/ast/InlineNode.js +263 -0
  59. package/dist/ast/InlineNode.js.map +1 -0
  60. package/dist/ast/MacroNode.d.ts +267 -0
  61. package/dist/ast/MacroNode.d.ts.map +1 -0
  62. package/dist/ast/MacroNode.js +164 -0
  63. package/dist/ast/MacroNode.js.map +1 -0
  64. package/dist/ast/index.d.ts +10 -0
  65. package/dist/ast/index.d.ts.map +1 -0
  66. package/dist/ast/index.js +14 -0
  67. package/dist/ast/index.js.map +1 -0
  68. package/dist/bin.js +33 -149
  69. package/dist/bin.js.map +1 -1
  70. package/dist/commands/auth.d.ts +15 -0
  71. package/dist/commands/auth.d.ts.map +1 -0
  72. package/dist/commands/auth.js +86 -0
  73. package/dist/commands/auth.js.map +1 -0
  74. package/dist/commands/clone.d.ts +12 -0
  75. package/dist/commands/clone.d.ts.map +1 -0
  76. package/dist/commands/clone.js +93 -0
  77. package/dist/commands/clone.js.map +1 -0
  78. package/dist/commands/delete.d.ts +13 -0
  79. package/dist/commands/delete.d.ts.map +1 -0
  80. package/dist/commands/delete.js +48 -0
  81. package/dist/commands/delete.js.map +1 -0
  82. package/dist/commands/errorHandler.d.ts +14 -0
  83. package/dist/commands/errorHandler.d.ts.map +1 -0
  84. package/dist/commands/errorHandler.js +33 -0
  85. package/dist/commands/errorHandler.js.map +1 -0
  86. package/dist/commands/git.d.ts +22 -0
  87. package/dist/commands/git.d.ts.map +1 -0
  88. package/dist/commands/git.js +72 -0
  89. package/dist/commands/git.js.map +1 -0
  90. package/dist/commands/index.d.ts +11 -0
  91. package/dist/commands/index.d.ts.map +1 -0
  92. package/dist/commands/index.js +11 -0
  93. package/dist/commands/index.js.map +1 -0
  94. package/dist/commands/layers.d.ts +31 -0
  95. package/dist/commands/layers.d.ts.map +1 -0
  96. package/dist/commands/layers.js +137 -0
  97. package/dist/commands/layers.js.map +1 -0
  98. package/dist/commands/new.d.ts +9 -0
  99. package/dist/commands/new.d.ts.map +1 -0
  100. package/dist/commands/new.js +80 -0
  101. package/dist/commands/new.js.map +1 -0
  102. package/dist/commands/pageTree.d.ts +18 -0
  103. package/dist/commands/pageTree.d.ts.map +1 -0
  104. package/dist/commands/pageTree.js +20 -0
  105. package/dist/commands/pageTree.js.map +1 -0
  106. package/dist/commands/shared.d.ts +15 -0
  107. package/dist/commands/shared.d.ts.map +1 -0
  108. package/dist/commands/shared.js +27 -0
  109. package/dist/commands/shared.js.map +1 -0
  110. package/dist/commands/sync.d.ts +15 -0
  111. package/dist/commands/sync.d.ts.map +1 -0
  112. package/dist/commands/sync.js +101 -0
  113. package/dist/commands/sync.js.map +1 -0
  114. package/dist/index.d.ts +10 -1
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +14 -0
  117. package/dist/index.js.map +1 -1
  118. package/dist/internal/NodeLayers.d.ts +7 -0
  119. package/dist/internal/NodeLayers.d.ts.map +1 -0
  120. package/dist/internal/NodeLayers.js +19 -0
  121. package/dist/internal/NodeLayers.js.map +1 -0
  122. package/dist/internal/frontmatter.d.ts +10 -0
  123. package/dist/internal/frontmatter.d.ts.map +1 -1
  124. package/dist/internal/frontmatter.js +16 -0
  125. package/dist/internal/frontmatter.js.map +1 -1
  126. package/dist/internal/gitCommands.d.ts +78 -0
  127. package/dist/internal/gitCommands.d.ts.map +1 -0
  128. package/dist/internal/gitCommands.js +156 -0
  129. package/dist/internal/gitCommands.js.map +1 -0
  130. package/dist/internal/hashUtils.d.ts +42 -1
  131. package/dist/internal/hashUtils.d.ts.map +1 -1
  132. package/dist/internal/hashUtils.js +38 -2
  133. package/dist/internal/hashUtils.js.map +1 -1
  134. package/dist/internal/oauthServer.d.ts +55 -0
  135. package/dist/internal/oauthServer.d.ts.map +1 -0
  136. package/dist/internal/oauthServer.js +110 -0
  137. package/dist/internal/oauthServer.js.map +1 -0
  138. package/dist/internal/pathUtils.d.ts +21 -4
  139. package/dist/internal/pathUtils.d.ts.map +1 -1
  140. package/dist/internal/pathUtils.js +24 -13
  141. package/dist/internal/pathUtils.js.map +1 -1
  142. package/dist/internal/tokenStorage.d.ts +75 -0
  143. package/dist/internal/tokenStorage.d.ts.map +1 -0
  144. package/dist/internal/tokenStorage.js +149 -0
  145. package/dist/internal/tokenStorage.js.map +1 -0
  146. package/dist/internal/userCache.d.ts +42 -0
  147. package/dist/internal/userCache.d.ts.map +1 -0
  148. package/dist/internal/userCache.js +51 -0
  149. package/dist/internal/userCache.js.map +1 -0
  150. package/dist/parsers/ConfluenceParser.d.ts +26 -0
  151. package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
  152. package/dist/parsers/ConfluenceParser.js +792 -0
  153. package/dist/parsers/ConfluenceParser.js.map +1 -0
  154. package/dist/parsers/MarkdownParser.d.ts +26 -0
  155. package/dist/parsers/MarkdownParser.d.ts.map +1 -0
  156. package/dist/parsers/MarkdownParser.js +873 -0
  157. package/dist/parsers/MarkdownParser.js.map +1 -0
  158. package/dist/parsers/index.d.ts +8 -0
  159. package/dist/parsers/index.d.ts.map +1 -0
  160. package/dist/parsers/index.js +8 -0
  161. package/dist/parsers/index.js.map +1 -0
  162. package/dist/schemas/ConfluenceSchema.d.ts +21 -0
  163. package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
  164. package/dist/schemas/ConfluenceSchema.js +38 -0
  165. package/dist/schemas/ConfluenceSchema.js.map +1 -0
  166. package/dist/schemas/ConversionSchema.d.ts +35 -0
  167. package/dist/schemas/ConversionSchema.d.ts.map +1 -0
  168. package/dist/schemas/ConversionSchema.js +208 -0
  169. package/dist/schemas/ConversionSchema.js.map +1 -0
  170. package/dist/schemas/MarkdownSchema.d.ts +21 -0
  171. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  172. package/dist/schemas/MarkdownSchema.js +38 -0
  173. package/dist/schemas/MarkdownSchema.js.map +1 -0
  174. package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
  175. package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
  176. package/dist/schemas/hast/HastFromHtml.js +107 -0
  177. package/dist/schemas/hast/HastFromHtml.js.map +1 -0
  178. package/dist/schemas/hast/HastSchema.d.ts +195 -0
  179. package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
  180. package/dist/schemas/hast/HastSchema.js +183 -0
  181. package/dist/schemas/hast/HastSchema.js.map +1 -0
  182. package/dist/schemas/hast/index.d.ts +9 -0
  183. package/dist/schemas/hast/index.d.ts.map +1 -0
  184. package/dist/schemas/hast/index.js +3 -0
  185. package/dist/schemas/hast/index.js.map +1 -0
  186. package/dist/schemas/index.d.ts +14 -0
  187. package/dist/schemas/index.d.ts.map +1 -0
  188. package/dist/schemas/index.js +16 -0
  189. package/dist/schemas/index.js.map +1 -0
  190. package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
  191. package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
  192. package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
  193. package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
  194. package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
  195. package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
  196. package/dist/schemas/mdast/MdastSchema.js +266 -0
  197. package/dist/schemas/mdast/MdastSchema.js.map +1 -0
  198. package/dist/schemas/mdast/index.d.ts +10 -0
  199. package/dist/schemas/mdast/index.d.ts.map +1 -0
  200. package/dist/schemas/mdast/index.js +4 -0
  201. package/dist/schemas/mdast/index.js.map +1 -0
  202. package/dist/schemas/mdast/mdastToString.d.ts +13 -0
  203. package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
  204. package/dist/schemas/mdast/mdastToString.js +85 -0
  205. package/dist/schemas/mdast/mdastToString.js.map +1 -0
  206. package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
  207. package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
  208. package/dist/schemas/nodes/block/BlockSchema.js +634 -0
  209. package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
  210. package/dist/schemas/nodes/block/index.d.ts +7 -0
  211. package/dist/schemas/nodes/block/index.d.ts.map +1 -0
  212. package/dist/schemas/nodes/block/index.js +7 -0
  213. package/dist/schemas/nodes/block/index.js.map +1 -0
  214. package/dist/schemas/nodes/index.d.ts +9 -0
  215. package/dist/schemas/nodes/index.d.ts.map +1 -0
  216. package/dist/schemas/nodes/index.js +12 -0
  217. package/dist/schemas/nodes/index.js.map +1 -0
  218. package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
  219. package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
  220. package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
  221. package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
  222. package/dist/schemas/nodes/inline/index.d.ts +7 -0
  223. package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
  224. package/dist/schemas/nodes/inline/index.js +7 -0
  225. package/dist/schemas/nodes/inline/index.js.map +1 -0
  226. package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
  227. package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
  228. package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
  229. package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
  230. package/dist/schemas/nodes/macro/index.d.ts +7 -0
  231. package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
  232. package/dist/schemas/nodes/macro/index.js +7 -0
  233. package/dist/schemas/nodes/macro/index.js.map +1 -0
  234. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
  235. package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
  236. package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
  237. package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
  238. package/dist/schemas/preprocessing/index.d.ts +8 -0
  239. package/dist/schemas/preprocessing/index.d.ts.map +1 -0
  240. package/dist/schemas/preprocessing/index.js +2 -0
  241. package/dist/schemas/preprocessing/index.js.map +1 -0
  242. package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
  243. package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
  244. package/dist/serializers/ConfluenceSerializer.js +551 -0
  245. package/dist/serializers/ConfluenceSerializer.js.map +1 -0
  246. package/dist/serializers/MarkdownSerializer.d.ts +34 -0
  247. package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
  248. package/dist/serializers/MarkdownSerializer.js +355 -0
  249. package/dist/serializers/MarkdownSerializer.js.map +1 -0
  250. package/dist/serializers/index.d.ts +8 -0
  251. package/dist/serializers/index.d.ts.map +1 -0
  252. package/dist/serializers/index.js +8 -0
  253. package/dist/serializers/index.js.map +1 -0
  254. package/package.json +32 -21
  255. package/src/ConfluenceAuth.ts +581 -0
  256. package/src/ConfluenceClient.ts +230 -165
  257. package/src/ConfluenceConfig.ts +63 -7
  258. package/src/ConfluenceError.ts +110 -14
  259. package/src/GitError.ts +92 -0
  260. package/src/GitService.ts +859 -0
  261. package/src/LocalFileSystem.ts +179 -9
  262. package/src/MarkdownConverter.ts +126 -122
  263. package/src/SchemaConverterError.ts +108 -0
  264. package/src/Schemas.ts +223 -6
  265. package/src/SyncEngine.ts +745 -162
  266. package/src/ast/BlockNode.ts +425 -0
  267. package/src/ast/Document.ts +90 -0
  268. package/src/ast/InlineNode.ts +323 -0
  269. package/src/ast/MacroNode.ts +245 -0
  270. package/src/ast/index.ts +83 -0
  271. package/src/bin.ts +50 -249
  272. package/src/commands/auth.ts +117 -0
  273. package/src/commands/clone.ts +145 -0
  274. package/src/commands/delete.ts +57 -0
  275. package/src/commands/errorHandler.ts +32 -0
  276. package/src/commands/git.ts +114 -0
  277. package/src/commands/index.ts +10 -0
  278. package/src/commands/layers.ts +211 -0
  279. package/src/commands/new.ts +99 -0
  280. package/src/commands/pageTree.ts +40 -0
  281. package/src/commands/shared.ts +35 -0
  282. package/src/commands/sync.ts +129 -0
  283. package/src/index.ts +21 -1
  284. package/src/internal/NodeLayers.ts +21 -0
  285. package/src/internal/frontmatter.ts +21 -0
  286. package/src/internal/gitCommands.ts +229 -0
  287. package/src/internal/hashUtils.ts +65 -3
  288. package/src/internal/oauthServer.ts +199 -0
  289. package/src/internal/pathUtils.ts +34 -17
  290. package/src/internal/tokenStorage.ts +240 -0
  291. package/src/internal/userCache.ts +90 -0
  292. package/src/parsers/ConfluenceParser.ts +950 -0
  293. package/src/parsers/MarkdownParser.ts +1198 -0
  294. package/src/parsers/index.ts +8 -0
  295. package/src/schemas/ConfluenceSchema.ts +56 -0
  296. package/src/schemas/ConversionSchema.ts +318 -0
  297. package/src/schemas/MarkdownSchema.ts +56 -0
  298. package/src/schemas/hast/HastFromHtml.ts +153 -0
  299. package/src/schemas/hast/HastSchema.ts +274 -0
  300. package/src/schemas/hast/index.ts +35 -0
  301. package/src/schemas/index.ts +20 -0
  302. package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
  303. package/src/schemas/mdast/MdastSchema.ts +566 -0
  304. package/src/schemas/mdast/index.ts +59 -0
  305. package/src/schemas/mdast/mdastToString.ts +102 -0
  306. package/src/schemas/nodes/block/BlockSchema.ts +773 -0
  307. package/src/schemas/nodes/block/index.ts +13 -0
  308. package/src/schemas/nodes/index.ts +20 -0
  309. package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
  310. package/src/schemas/nodes/inline/index.ts +14 -0
  311. package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
  312. package/src/schemas/nodes/macro/index.ts +6 -0
  313. package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
  314. package/src/schemas/preprocessing/index.ts +8 -0
  315. package/src/serializers/ConfluenceSerializer.ts +717 -0
  316. package/src/serializers/MarkdownSerializer.ts +493 -0
  317. package/src/serializers/index.ts +8 -0
  318. package/test/GitService.test.ts +209 -0
  319. package/test/MarkdownConverter.test.ts +37 -3
  320. package/test/Schemas.test.ts +97 -2
  321. package/test/ast/BlockNode.test.ts +265 -0
  322. package/test/ast/Document.test.ts +126 -0
  323. package/test/ast/InlineNode.test.ts +161 -0
  324. package/test/fixtures/integration-test.html.fixture +103 -0
  325. package/test/fixtures/integration-test.md.expected +257 -0
  326. package/test/integration.test.ts +269 -0
  327. package/test/oauthServer.test.ts +50 -0
  328. package/test/parsers/ConfluenceParser.test.ts +283 -0
  329. package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
  330. package/test/schemas/ConversionSchema.test.ts +159 -0
  331. package/test/schemas/HastSchema.test.ts +138 -0
  332. package/test/schemas/MdastSchema.test.ts +145 -0
  333. package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
  334. package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
  335. package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
  336. package/test/tokenStorage.test.ts +99 -0
@@ -0,0 +1,180 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Either from "effect/Either"
4
+ import * as Schema from "effect/Schema"
5
+ import { PreprocessedHtmlFromConfluence, PreprocessedHtmlSchema } from "../../src/schemas/preprocessing/index.js"
6
+
7
+ describe("PreprocessedHtmlSchema", () => {
8
+ it("validates branded string", () => {
9
+ const result = Schema.decodeUnknownEither(PreprocessedHtmlSchema)("<p>Hello</p>")
10
+ expect(Either.isRight(result)).toBe(true)
11
+ })
12
+ })
13
+
14
+ describe("PreprocessedHtmlFromConfluence", () => {
15
+ describe("size limit", () => {
16
+ it.effect("rejects HTML exceeding size limit", () =>
17
+ Effect.gen(function*() {
18
+ const largeHtml = "x".repeat(1024 * 1024 + 1)
19
+ const result = yield* Effect.either(Schema.decode(PreprocessedHtmlFromConfluence)(largeHtml))
20
+ expect(Either.isLeft(result)).toBe(true)
21
+ }))
22
+
23
+ it.effect("accepts HTML within size limit", () =>
24
+ Effect.gen(function*() {
25
+ const html = "<p>Hello</p>"
26
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
27
+ expect(result).toContain("Hello")
28
+ }))
29
+ })
30
+
31
+ describe("structured macro expansion", () => {
32
+ it.effect("expands code macro", () =>
33
+ Effect.gen(function*() {
34
+ // Single line format that matches the regex pattern
35
+ const html =
36
+ `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">typescript</ac:parameter><ac:plain-text-body><![CDATA[const x = 1]]></ac:plain-text-body></ac:structured-macro>`
37
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
38
+ expect(result).toContain("data-macro=\"code\"")
39
+ expect(result).toContain("data-language=\"typescript\"")
40
+ expect(result).toContain("const x = 1")
41
+ }))
42
+
43
+ it.effect("expands info panel macro", () =>
44
+ Effect.gen(function*() {
45
+ const html = `<ac:structured-macro ac:name="info">
46
+ <ac:parameter ac:name="title">Note</ac:parameter>
47
+ <ac:rich-text-body><p>Content here</p></ac:rich-text-body>
48
+ </ac:structured-macro>`
49
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
50
+ expect(result).toContain("data-macro=\"info\"")
51
+ expect(result).toContain("data-title=\"Note\"")
52
+ expect(result).toContain("<p>Content here</p>")
53
+ }))
54
+
55
+ it.effect("expands expand macro", () =>
56
+ Effect.gen(function*() {
57
+ const html = `<ac:structured-macro ac:name="expand">
58
+ <ac:parameter ac:name="title">Click to expand</ac:parameter>
59
+ <ac:rich-text-body><p>Hidden content</p></ac:rich-text-body>
60
+ </ac:structured-macro>`
61
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
62
+ expect(result).toContain("<details")
63
+ expect(result).toContain("<summary>Click to expand</summary>")
64
+ expect(result).toContain("<p>Hidden content</p>")
65
+ }))
66
+
67
+ it.effect("expands TOC macro", () =>
68
+ Effect.gen(function*() {
69
+ const html = `<ac:structured-macro ac:name="toc">
70
+ <ac:parameter ac:name="minLevel">2</ac:parameter>
71
+ <ac:parameter ac:name="maxLevel">4</ac:parameter>
72
+ </ac:structured-macro>`
73
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
74
+ expect(result).toContain("data-macro=\"toc\"")
75
+ expect(result).toContain("data-min=\"2\"")
76
+ expect(result).toContain("data-max=\"4\"")
77
+ }))
78
+
79
+ it.effect("expands status macro", () =>
80
+ Effect.gen(function*() {
81
+ const html = `<ac:structured-macro ac:name="status">
82
+ <ac:parameter ac:name="colour">Green</ac:parameter>
83
+ <ac:parameter ac:name="title">Done</ac:parameter>
84
+ </ac:structured-macro>`
85
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
86
+ expect(result).toContain("data-macro=\"status\"")
87
+ expect(result).toContain("data-color=\"Green\"")
88
+ expect(result).toContain("Done")
89
+ }))
90
+ })
91
+
92
+ describe("task list preprocessing", () => {
93
+ it.effect("converts task list to ul", () =>
94
+ Effect.gen(function*() {
95
+ const html = `<ac:task-list>
96
+ <ac:task>
97
+ <ac:task-id>1</ac:task-id>
98
+ <ac:task-status>incomplete</ac:task-status>
99
+ <ac:task-body>Task one</ac:task-body>
100
+ </ac:task>
101
+ </ac:task-list>`
102
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
103
+ expect(result).toContain("<ul data-macro=\"task-list\">")
104
+ expect(result).toContain("data-task-id=\"1\"")
105
+ expect(result).toContain("data-task-status=\"incomplete\"")
106
+ expect(result).toContain("Task one")
107
+ }))
108
+ })
109
+
110
+ describe("image preprocessing", () => {
111
+ it.effect("converts attachment images", () =>
112
+ Effect.gen(function*() {
113
+ const html = `<ac:image ac:align="center" ac:width="250">
114
+ <ri:attachment ri:filename="test.png"/>
115
+ </ac:image>`
116
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
117
+ expect(result).toContain("data-attachment=\"test.png\"")
118
+ expect(result).toContain("data-align=\"center\"")
119
+ expect(result).toContain("data-width=\"250\"")
120
+ }))
121
+ })
122
+
123
+ describe("emoticon preprocessing", () => {
124
+ it.effect("converts emoticons", () =>
125
+ Effect.gen(function*() {
126
+ const html = `<ac:emoticon ac:emoji-shortname=":grinning:" ac:emoji-id="1f600" ac:emoji-fallback="😀"/>`
127
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
128
+ expect(result).toContain("data-emoji=\":grinning:\"")
129
+ expect(result).toContain("data-emoji-id=\"1f600\"")
130
+ expect(result).toContain("😀")
131
+ }))
132
+ })
133
+
134
+ describe("user mention preprocessing", () => {
135
+ it.effect("converts user mentions", () =>
136
+ Effect.gen(function*() {
137
+ const html = `<ac:link><ri:user ri:account-id="557058:abc123"/></ac:link>`
138
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
139
+ expect(result).toContain("data-user-mention=\"557058:abc123\"")
140
+ }))
141
+ })
142
+
143
+ describe("namespace stripping", () => {
144
+ it.effect("removes ac: and ri: tags", () =>
145
+ Effect.gen(function*() {
146
+ const html = `<p>Text<ac:parameter ac:name="foo">bar</ac:parameter></p>`
147
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
148
+ expect(result).not.toContain("ac:parameter")
149
+ expect(result).toContain("<p>Text</p>")
150
+ }))
151
+ })
152
+
153
+ describe("layout preprocessing", () => {
154
+ it.effect("adds layout markers", () =>
155
+ Effect.gen(function*() {
156
+ const html = `<ac:layout>
157
+ <ac:layout-section ac:type="two_equal">
158
+ <ac:layout-cell><p>Cell 1</p></ac:layout-cell>
159
+ <ac:layout-cell><p>Cell 2</p></ac:layout-cell>
160
+ </ac:layout-section>
161
+ </ac:layout>`
162
+ const result = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
163
+ expect(result).toContain("<!--cf:layout-start-->")
164
+ expect(result).toContain("<!--cf:layout-end-->")
165
+ expect(result).toContain("<!--cf:section:0;")
166
+ expect(result).toContain("<p>Cell 1</p>")
167
+ expect(result).toContain("<p>Cell 2</p>")
168
+ }))
169
+ })
170
+
171
+ describe("encode (identity)", () => {
172
+ it.effect("encode returns same string", () =>
173
+ Effect.gen(function*() {
174
+ const html = "<p>Hello</p>"
175
+ const preprocessed = yield* Schema.decode(PreprocessedHtmlFromConfluence)(html)
176
+ const encoded = yield* Schema.encode(PreprocessedHtmlFromConfluence)(preprocessed)
177
+ expect(encoded).toBe(preprocessed)
178
+ }))
179
+ })
180
+ })
@@ -0,0 +1,159 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import { Effect, Schema } from "effect"
3
+ import { ConfluenceToMarkdown, DocumentFromHast, DocumentFromMdast } from "../../src/schemas/ConversionSchema.js"
4
+ import { HastFromHtml } from "../../src/schemas/hast/index.js"
5
+ import { MdastFromMarkdown } from "../../src/schemas/mdast/index.js"
6
+
7
+ describe("ConversionSchema", () => {
8
+ describe("DocumentFromHast", () => {
9
+ it.effect("converts simple HAST root to Document", () =>
10
+ Effect.gen(function*() {
11
+ const html = "<h1>Title</h1><p>Hello world</p>"
12
+ const hast = yield* Schema.decode(HastFromHtml)(html)
13
+ const doc = yield* Schema.decode(DocumentFromHast)(hast)
14
+
15
+ expect(doc.version).toBe(1)
16
+ expect(doc.children.length).toBe(2)
17
+ expect(doc.children[0]?._tag).toBe("Heading")
18
+ expect(doc.children[1]?._tag).toBe("Paragraph")
19
+ }))
20
+
21
+ it.effect("handles code blocks", () =>
22
+ Effect.gen(function*() {
23
+ const html = "<pre><code>const x = 1</code></pre>"
24
+ const hast = yield* Schema.decode(HastFromHtml)(html)
25
+ const doc = yield* Schema.decode(DocumentFromHast)(hast)
26
+
27
+ expect(doc.children[0]?._tag).toBe("CodeBlock")
28
+ }))
29
+
30
+ it.effect("handles empty input", () =>
31
+ Effect.gen(function*() {
32
+ const html = ""
33
+ const hast = yield* Schema.decode(HastFromHtml)(html)
34
+ const doc = yield* Schema.decode(DocumentFromHast)(hast)
35
+
36
+ expect(doc.children.length).toBe(0)
37
+ }))
38
+ })
39
+
40
+ describe("DocumentFromMdast", () => {
41
+ it.effect("converts simple MDAST root to Document", () =>
42
+ Effect.gen(function*() {
43
+ const md = "# Title\n\nHello world"
44
+ const mdast = yield* Schema.decode(MdastFromMarkdown)(md)
45
+ const doc = yield* Schema.decode(DocumentFromMdast)(mdast)
46
+
47
+ expect(doc.version).toBe(1)
48
+ expect(doc.children.length).toBe(2)
49
+ expect(doc.children[0]?._tag).toBe("Heading")
50
+ expect(doc.children[1]?._tag).toBe("Paragraph")
51
+ }))
52
+
53
+ it.effect("handles code blocks", () =>
54
+ Effect.gen(function*() {
55
+ const md = "```ts\nconst x = 1\n```"
56
+ const mdast = yield* Schema.decode(MdastFromMarkdown)(md)
57
+ const doc = yield* Schema.decode(DocumentFromMdast)(mdast)
58
+
59
+ expect(doc.children[0]?._tag).toBe("CodeBlock")
60
+ }))
61
+
62
+ it.effect("handles lists", () =>
63
+ Effect.gen(function*() {
64
+ const md = "- Item 1\n- Item 2"
65
+ const mdast = yield* Schema.decode(MdastFromMarkdown)(md)
66
+ const doc = yield* Schema.decode(DocumentFromMdast)(mdast)
67
+
68
+ expect(doc.children[0]?._tag).toBe("List")
69
+ }))
70
+ })
71
+
72
+ describe("ConfluenceToMarkdown", () => {
73
+ it.effect("decodes simple Confluence HTML to Markdown", () =>
74
+ Effect.gen(function*() {
75
+ const html = "<h1>Title</h1><p>Hello world</p>"
76
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
77
+
78
+ expect(md).toContain("# Title")
79
+ expect(md).toContain("Hello world")
80
+ }))
81
+
82
+ it.effect("decodes code blocks", () =>
83
+ Effect.gen(function*() {
84
+ const html = "<pre><code class=\"language-ts\">const x = 1</code></pre>"
85
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
86
+
87
+ expect(md).toContain("```")
88
+ expect(md).toContain("const x = 1")
89
+ }))
90
+
91
+ it.effect("decodes inline formatting", () =>
92
+ Effect.gen(function*() {
93
+ const html = "<p><strong>bold</strong> and <em>italic</em></p>"
94
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
95
+
96
+ expect(md).toContain("**bold**")
97
+ expect(md).toContain("_italic_")
98
+ }))
99
+
100
+ it.effect("encodes Markdown to basic HTML (structure only)", () =>
101
+ Effect.gen(function*() {
102
+ const md = "# Title\n\nHello world"
103
+ const html = yield* Schema.encode(ConfluenceToMarkdown)(md)
104
+
105
+ // Note: basic HTML encoding only preserves structure, not content
106
+ expect(html).toContain("<h1>")
107
+ expect(html).toContain("<p>")
108
+ }))
109
+
110
+ it.effect("handles links", () =>
111
+ Effect.gen(function*() {
112
+ const html = "<p><a href=\"https://example.com\">Click here</a></p>"
113
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
114
+
115
+ expect(md).toContain("[Click here](https://example.com)")
116
+ }))
117
+
118
+ it.effect("handles horizontal rules", () =>
119
+ Effect.gen(function*() {
120
+ const html = "<hr />"
121
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
122
+
123
+ expect(md).toContain("---")
124
+ }))
125
+
126
+ it.effect("handles lists", () =>
127
+ Effect.gen(function*() {
128
+ const html = "<ul><li><p>Item 1</p></li><li><p>Item 2</p></li></ul>"
129
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
130
+
131
+ expect(md).toContain("- ")
132
+ expect(md).toContain("Item 1")
133
+ expect(md).toContain("Item 2")
134
+ }))
135
+
136
+ it.effect("handles tables", () =>
137
+ Effect.gen(function*() {
138
+ const html = "<table><tr><th>A</th></tr><tr><td>1</td></tr></table>"
139
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(html)
140
+
141
+ expect(md).toContain("|")
142
+ expect(md).toContain("---")
143
+ }))
144
+ })
145
+
146
+ describe("roundtrip", () => {
147
+ it.effect("HTML -> Markdown -> HTML preserves structure", () =>
148
+ Effect.gen(function*() {
149
+ // Start from HTML since that direction is fully supported
150
+ const originalHtml = "<h1>Hello</h1><p>World</p>"
151
+ const md = yield* Schema.decode(ConfluenceToMarkdown)(originalHtml)
152
+ const html = yield* Schema.encode(ConfluenceToMarkdown)(md)
153
+
154
+ // Structure is preserved
155
+ expect(html).toContain("<h1>")
156
+ expect(html).toContain("<p>")
157
+ }))
158
+ })
159
+ })
@@ -0,0 +1,138 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Either from "effect/Either"
4
+ import * as Schema from "effect/Schema"
5
+ import {
6
+ getTextContent,
7
+ HastFromHtml,
8
+ HastNodeSchema,
9
+ HastRootSchema,
10
+ isHastElement,
11
+ isHastText,
12
+ makeHastElement,
13
+ makeHastRoot,
14
+ makeHastText
15
+ } from "../../src/schemas/hast/index.js"
16
+
17
+ describe("HastSchema", () => {
18
+ describe("constructors", () => {
19
+ it("creates text node", () => {
20
+ const node = makeHastText("hello")
21
+ expect(node._tag).toBe("text")
22
+ expect(node.value).toBe("hello")
23
+ })
24
+
25
+ it("creates element node", () => {
26
+ const node = makeHastElement("div", { class: "test" }, [makeHastText("child")])
27
+ expect(node._tag).toBe("element")
28
+ expect(node.tagName).toBe("div")
29
+ expect(node.properties.class).toBe("test")
30
+ expect(node.children).toHaveLength(1)
31
+ })
32
+
33
+ it("creates root node", () => {
34
+ const root = makeHastRoot([makeHastElement("p", {}, [makeHastText("text")])])
35
+ expect(root._tag).toBe("root")
36
+ expect(root.children).toHaveLength(1)
37
+ })
38
+ })
39
+
40
+ describe("type guards", () => {
41
+ it("isHastText returns true for text node", () => {
42
+ expect(isHastText(makeHastText("hello"))).toBe(true)
43
+ expect(isHastText(makeHastElement("div"))).toBe(false)
44
+ })
45
+
46
+ it("isHastElement returns true for element node", () => {
47
+ expect(isHastElement(makeHastElement("div"))).toBe(true)
48
+ expect(isHastElement(makeHastText("hello"))).toBe(false)
49
+ })
50
+ })
51
+
52
+ describe("getTextContent", () => {
53
+ it("extracts text from text node", () => {
54
+ expect(getTextContent(makeHastText("hello"))).toBe("hello")
55
+ })
56
+
57
+ it("extracts text from nested elements", () => {
58
+ const el = makeHastElement("div", {}, [
59
+ makeHastText("hello "),
60
+ makeHastElement("strong", {}, [makeHastText("world")])
61
+ ])
62
+ expect(getTextContent(el)).toBe("hello world")
63
+ })
64
+ })
65
+
66
+ describe("HastNodeSchema", () => {
67
+ it("validates text node", () => {
68
+ const result = Schema.decodeUnknownEither(HastNodeSchema)(makeHastText("test"))
69
+ expect(Either.isRight(result)).toBe(true)
70
+ })
71
+
72
+ it("validates element node", () => {
73
+ const result = Schema.decodeUnknownEither(HastNodeSchema)(makeHastElement("p"))
74
+ expect(Either.isRight(result)).toBe(true)
75
+ })
76
+ })
77
+
78
+ describe("HastRootSchema", () => {
79
+ it("validates root node", () => {
80
+ const root = makeHastRoot([makeHastElement("p", {}, [makeHastText("text")])])
81
+ const result = Schema.decodeUnknownEither(HastRootSchema)(root)
82
+ expect(Either.isRight(result)).toBe(true)
83
+ })
84
+ })
85
+ })
86
+
87
+ describe("HastFromHtml", () => {
88
+ describe("decode (HTML -> HAST)", () => {
89
+ it.effect("parses simple HTML", () =>
90
+ Effect.gen(function*() {
91
+ const hast = yield* Schema.decode(HastFromHtml)("<p>Hello</p>")
92
+ expect(hast._tag).toBe("root")
93
+ expect(hast.children).toHaveLength(1)
94
+ const p = hast.children[0]
95
+ expect(isHastElement(p) && p.tagName).toBe("p")
96
+ }))
97
+
98
+ it.effect("parses nested HTML", () =>
99
+ Effect.gen(function*() {
100
+ const hast = yield* Schema.decode(HastFromHtml)("<div><p>Text</p></div>")
101
+ expect(hast.children).toHaveLength(1)
102
+ const div = hast.children[0]
103
+ if (isHastElement(div)) {
104
+ expect(div.tagName).toBe("div")
105
+ expect(div.children).toHaveLength(1)
106
+ }
107
+ }))
108
+
109
+ it.effect("parses HTML with attributes", () =>
110
+ Effect.gen(function*() {
111
+ const hast = yield* Schema.decode(HastFromHtml)("<a href=\"https://example.com\">Link</a>")
112
+ const a = hast.children[0]
113
+ if (isHastElement(a)) {
114
+ expect(a.properties.href).toBe("https://example.com")
115
+ }
116
+ }))
117
+ })
118
+
119
+ describe("encode (HAST -> HTML)", () => {
120
+ it.effect("stringifies HAST to HTML", () =>
121
+ Effect.gen(function*() {
122
+ const hast = makeHastRoot([
123
+ makeHastElement("p", {}, [makeHastText("Hello")])
124
+ ])
125
+ const html = yield* Schema.encode(HastFromHtml)(hast)
126
+ expect(html).toContain("<p>")
127
+ expect(html).toContain("Hello")
128
+ }))
129
+
130
+ it.effect("roundtrips HTML", () =>
131
+ Effect.gen(function*() {
132
+ const original = "<p>Test paragraph</p>"
133
+ const hast = yield* Schema.decode(HastFromHtml)(original)
134
+ const html = yield* Schema.encode(HastFromHtml)(hast)
135
+ expect(html.replace(/\s/g, "")).toBe(original.replace(/\s/g, ""))
136
+ }))
137
+ })
138
+ })
@@ -0,0 +1,145 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Either from "effect/Either"
4
+ import * as Schema from "effect/Schema"
5
+ import {
6
+ isMdastHeading,
7
+ isMdastParagraph,
8
+ isMdastText,
9
+ makeMdastHeading,
10
+ makeMdastParagraph,
11
+ makeMdastRoot,
12
+ makeMdastText,
13
+ MdastFromMarkdown,
14
+ MdastRootSchema,
15
+ MdastTextSchema
16
+ } from "../../src/schemas/mdast/index.js"
17
+
18
+ describe("MdastSchema", () => {
19
+ describe("constructors", () => {
20
+ it("creates text node", () => {
21
+ const node = makeMdastText("hello")
22
+ expect(node.type).toBe("text")
23
+ expect(node.value).toBe("hello")
24
+ })
25
+
26
+ it("creates heading node", () => {
27
+ const node = makeMdastHeading(2, [makeMdastText("Title")])
28
+ expect(node.type).toBe("heading")
29
+ expect(node.depth).toBe(2)
30
+ expect(node.children).toHaveLength(1)
31
+ })
32
+
33
+ it("creates paragraph node", () => {
34
+ const node = makeMdastParagraph([makeMdastText("content")])
35
+ expect(node.type).toBe("paragraph")
36
+ expect(node.children).toHaveLength(1)
37
+ })
38
+
39
+ it("creates root node", () => {
40
+ const root = makeMdastRoot([makeMdastParagraph([makeMdastText("text")])])
41
+ expect(root.type).toBe("root")
42
+ expect(root.children).toHaveLength(1)
43
+ })
44
+ })
45
+
46
+ describe("type guards", () => {
47
+ it("isMdastText returns true for text node", () => {
48
+ expect(isMdastText(makeMdastText("hello"))).toBe(true)
49
+ expect(isMdastText(makeMdastHeading(1, []))).toBe(false)
50
+ })
51
+
52
+ it("isMdastHeading returns true for heading node", () => {
53
+ expect(isMdastHeading(makeMdastHeading(1, []))).toBe(true)
54
+ expect(isMdastHeading(makeMdastText("hello"))).toBe(false)
55
+ })
56
+
57
+ it("isMdastParagraph returns true for paragraph node", () => {
58
+ expect(isMdastParagraph(makeMdastParagraph([]))).toBe(true)
59
+ expect(isMdastParagraph(makeMdastText("hello"))).toBe(false)
60
+ })
61
+ })
62
+
63
+ describe("MdastTextSchema", () => {
64
+ it("validates text node", () => {
65
+ const result = Schema.decodeUnknownEither(MdastTextSchema)(makeMdastText("test"))
66
+ expect(Either.isRight(result)).toBe(true)
67
+ })
68
+
69
+ it("rejects invalid type", () => {
70
+ const result = Schema.decodeUnknownEither(MdastTextSchema)({ type: "other", value: "x" })
71
+ expect(Either.isLeft(result)).toBe(true)
72
+ })
73
+ })
74
+
75
+ describe("MdastRootSchema", () => {
76
+ it("validates root node", () => {
77
+ const root = makeMdastRoot([makeMdastParagraph([makeMdastText("text")])])
78
+ const result = Schema.decodeUnknownEither(MdastRootSchema)(root)
79
+ expect(Either.isRight(result)).toBe(true)
80
+ })
81
+ })
82
+ })
83
+
84
+ describe("MdastFromMarkdown", () => {
85
+ describe("decode (Markdown -> MDAST)", () => {
86
+ it.effect("parses heading", () =>
87
+ Effect.gen(function*() {
88
+ const mdast = yield* Schema.decode(MdastFromMarkdown)("# Hello")
89
+ expect(mdast.type).toBe("root")
90
+ expect(mdast.children).toHaveLength(1)
91
+ const heading = mdast.children[0]
92
+ expect(heading.type).toBe("heading")
93
+ }))
94
+
95
+ it.effect("parses paragraph", () =>
96
+ Effect.gen(function*() {
97
+ const mdast = yield* Schema.decode(MdastFromMarkdown)("Hello world")
98
+ expect(mdast.children).toHaveLength(1)
99
+ expect(mdast.children[0].type).toBe("paragraph")
100
+ }))
101
+
102
+ it.effect("parses code block", () =>
103
+ Effect.gen(function*() {
104
+ const mdast = yield* Schema.decode(MdastFromMarkdown)("```ts\nconst x = 1\n```")
105
+ expect(mdast.children).toHaveLength(1)
106
+ const code = mdast.children[0]
107
+ expect(code.type).toBe("code")
108
+ }))
109
+
110
+ it.effect("parses list", () =>
111
+ Effect.gen(function*() {
112
+ const mdast = yield* Schema.decode(MdastFromMarkdown)("- item 1\n- item 2")
113
+ expect(mdast.children).toHaveLength(1)
114
+ expect(mdast.children[0].type).toBe("list")
115
+ }))
116
+ })
117
+
118
+ describe("encode (MDAST -> Markdown)", () => {
119
+ it.effect("stringifies heading", () =>
120
+ Effect.gen(function*() {
121
+ const mdast = makeMdastRoot([
122
+ makeMdastHeading(1, [makeMdastText("Title")])
123
+ ])
124
+ const md = yield* Schema.encode(MdastFromMarkdown)(mdast)
125
+ expect(md.trim()).toBe("# Title")
126
+ }))
127
+
128
+ it.effect("stringifies paragraph", () =>
129
+ Effect.gen(function*() {
130
+ const mdast = makeMdastRoot([
131
+ makeMdastParagraph([makeMdastText("Hello world")])
132
+ ])
133
+ const md = yield* Schema.encode(MdastFromMarkdown)(mdast)
134
+ expect(md.trim()).toBe("Hello world")
135
+ }))
136
+
137
+ it.effect("roundtrips markdown", () =>
138
+ Effect.gen(function*() {
139
+ const original = "# Title\n\nParagraph text."
140
+ const mdast = yield* Schema.decode(MdastFromMarkdown)(original)
141
+ const md = yield* Schema.encode(MdastFromMarkdown)(mdast)
142
+ expect(md.trim()).toBe(original)
143
+ }))
144
+ })
145
+ })