@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.
- package/CHANGELOG.md +73 -0
- package/LICENSE +21 -0
- package/README.md +282 -14
- package/dist/ConfluenceAuth.d.ts +76 -0
- package/dist/ConfluenceAuth.d.ts.map +1 -0
- package/dist/ConfluenceAuth.js +366 -0
- package/dist/ConfluenceAuth.js.map +1 -0
- package/dist/ConfluenceClient.d.ts +26 -12
- package/dist/ConfluenceClient.d.ts.map +1 -1
- package/dist/ConfluenceClient.js +139 -97
- package/dist/ConfluenceClient.js.map +1 -1
- package/dist/ConfluenceConfig.d.ts +4 -24
- package/dist/ConfluenceConfig.d.ts.map +1 -1
- package/dist/ConfluenceConfig.js +45 -7
- package/dist/ConfluenceConfig.js.map +1 -1
- package/dist/ConfluenceError.d.ts +99 -16
- package/dist/ConfluenceError.d.ts.map +1 -1
- package/dist/ConfluenceError.js +88 -5
- package/dist/ConfluenceError.js.map +1 -1
- package/dist/GitError.d.ts +103 -0
- package/dist/GitError.d.ts.map +1 -0
- package/dist/GitError.js +85 -0
- package/dist/GitError.js.map +1 -0
- package/dist/GitService.d.ts +175 -0
- package/dist/GitService.d.ts.map +1 -0
- package/dist/GitService.js +431 -0
- package/dist/GitService.js.map +1 -0
- package/dist/LocalFileSystem.d.ts +29 -4
- package/dist/LocalFileSystem.d.ts.map +1 -1
- package/dist/LocalFileSystem.js +80 -6
- package/dist/LocalFileSystem.js.map +1 -1
- package/dist/MarkdownConverter.d.ts +49 -2
- package/dist/MarkdownConverter.d.ts.map +1 -1
- package/dist/MarkdownConverter.js +73 -111
- package/dist/MarkdownConverter.js.map +1 -1
- package/dist/SchemaConverterError.d.ts +108 -0
- package/dist/SchemaConverterError.d.ts.map +1 -0
- package/dist/SchemaConverterError.js +84 -0
- package/dist/SchemaConverterError.js.map +1 -0
- package/dist/Schemas.d.ts +225 -1
- package/dist/Schemas.d.ts.map +1 -1
- package/dist/Schemas.js +155 -6
- package/dist/Schemas.js.map +1 -1
- package/dist/SyncEngine.d.ts +30 -20
- package/dist/SyncEngine.d.ts.map +1 -1
- package/dist/SyncEngine.js +566 -117
- package/dist/SyncEngine.js.map +1 -1
- package/dist/ast/BlockNode.d.ts +468 -0
- package/dist/ast/BlockNode.d.ts.map +1 -0
- package/dist/ast/BlockNode.js +319 -0
- package/dist/ast/BlockNode.js.map +1 -0
- package/dist/ast/Document.d.ts +244 -0
- package/dist/ast/Document.d.ts.map +1 -0
- package/dist/ast/Document.js +69 -0
- package/dist/ast/Document.js.map +1 -0
- package/dist/ast/InlineNode.d.ts +477 -0
- package/dist/ast/InlineNode.d.ts.map +1 -0
- package/dist/ast/InlineNode.js +263 -0
- package/dist/ast/InlineNode.js.map +1 -0
- package/dist/ast/MacroNode.d.ts +267 -0
- package/dist/ast/MacroNode.d.ts.map +1 -0
- package/dist/ast/MacroNode.js +164 -0
- package/dist/ast/MacroNode.js.map +1 -0
- package/dist/ast/index.d.ts +10 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +14 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/bin.js +33 -149
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +15 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +86 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/clone.d.ts +12 -0
- package/dist/commands/clone.d.ts.map +1 -0
- package/dist/commands/clone.js +93 -0
- package/dist/commands/clone.js.map +1 -0
- package/dist/commands/delete.d.ts +13 -0
- package/dist/commands/delete.d.ts.map +1 -0
- package/dist/commands/delete.js +48 -0
- package/dist/commands/delete.js.map +1 -0
- package/dist/commands/errorHandler.d.ts +14 -0
- package/dist/commands/errorHandler.d.ts.map +1 -0
- package/dist/commands/errorHandler.js +33 -0
- package/dist/commands/errorHandler.js.map +1 -0
- package/dist/commands/git.d.ts +22 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +72 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/layers.d.ts +31 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +137 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +80 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pageTree.d.ts +18 -0
- package/dist/commands/pageTree.d.ts.map +1 -0
- package/dist/commands/pageTree.js +20 -0
- package/dist/commands/pageTree.js.map +1 -0
- package/dist/commands/shared.d.ts +15 -0
- package/dist/commands/shared.d.ts.map +1 -0
- package/dist/commands/shared.js +27 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/sync.d.ts +15 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +101 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/NodeLayers.d.ts +7 -0
- package/dist/internal/NodeLayers.d.ts.map +1 -0
- package/dist/internal/NodeLayers.js +19 -0
- package/dist/internal/NodeLayers.js.map +1 -0
- package/dist/internal/frontmatter.d.ts +10 -0
- package/dist/internal/frontmatter.d.ts.map +1 -1
- package/dist/internal/frontmatter.js +16 -0
- package/dist/internal/frontmatter.js.map +1 -1
- package/dist/internal/gitCommands.d.ts +78 -0
- package/dist/internal/gitCommands.d.ts.map +1 -0
- package/dist/internal/gitCommands.js +156 -0
- package/dist/internal/gitCommands.js.map +1 -0
- package/dist/internal/hashUtils.d.ts +42 -1
- package/dist/internal/hashUtils.d.ts.map +1 -1
- package/dist/internal/hashUtils.js +38 -2
- package/dist/internal/hashUtils.js.map +1 -1
- package/dist/internal/oauthServer.d.ts +55 -0
- package/dist/internal/oauthServer.d.ts.map +1 -0
- package/dist/internal/oauthServer.js +110 -0
- package/dist/internal/oauthServer.js.map +1 -0
- package/dist/internal/pathUtils.d.ts +21 -4
- package/dist/internal/pathUtils.d.ts.map +1 -1
- package/dist/internal/pathUtils.js +24 -13
- package/dist/internal/pathUtils.js.map +1 -1
- package/dist/internal/tokenStorage.d.ts +75 -0
- package/dist/internal/tokenStorage.d.ts.map +1 -0
- package/dist/internal/tokenStorage.js +149 -0
- package/dist/internal/tokenStorage.js.map +1 -0
- package/dist/internal/userCache.d.ts +42 -0
- package/dist/internal/userCache.d.ts.map +1 -0
- package/dist/internal/userCache.js +51 -0
- package/dist/internal/userCache.js.map +1 -0
- package/dist/parsers/ConfluenceParser.d.ts +26 -0
- package/dist/parsers/ConfluenceParser.d.ts.map +1 -0
- package/dist/parsers/ConfluenceParser.js +792 -0
- package/dist/parsers/ConfluenceParser.js.map +1 -0
- package/dist/parsers/MarkdownParser.d.ts +26 -0
- package/dist/parsers/MarkdownParser.d.ts.map +1 -0
- package/dist/parsers/MarkdownParser.js +873 -0
- package/dist/parsers/MarkdownParser.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +8 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/schemas/ConfluenceSchema.d.ts +21 -0
- package/dist/schemas/ConfluenceSchema.d.ts.map +1 -0
- package/dist/schemas/ConfluenceSchema.js +38 -0
- package/dist/schemas/ConfluenceSchema.js.map +1 -0
- package/dist/schemas/ConversionSchema.d.ts +35 -0
- package/dist/schemas/ConversionSchema.d.ts.map +1 -0
- package/dist/schemas/ConversionSchema.js +208 -0
- package/dist/schemas/ConversionSchema.js.map +1 -0
- package/dist/schemas/MarkdownSchema.d.ts +21 -0
- package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
- package/dist/schemas/MarkdownSchema.js +38 -0
- package/dist/schemas/MarkdownSchema.js.map +1 -0
- package/dist/schemas/hast/HastFromHtml.d.ts +27 -0
- package/dist/schemas/hast/HastFromHtml.d.ts.map +1 -0
- package/dist/schemas/hast/HastFromHtml.js +107 -0
- package/dist/schemas/hast/HastFromHtml.js.map +1 -0
- package/dist/schemas/hast/HastSchema.d.ts +195 -0
- package/dist/schemas/hast/HastSchema.d.ts.map +1 -0
- package/dist/schemas/hast/HastSchema.js +183 -0
- package/dist/schemas/hast/HastSchema.js.map +1 -0
- package/dist/schemas/hast/index.d.ts +9 -0
- package/dist/schemas/hast/index.d.ts.map +1 -0
- package/dist/schemas/hast/index.js +3 -0
- package/dist/schemas/hast/index.js.map +1 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +16 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts +30 -0
- package/dist/schemas/mdast/MdastFromMarkdown.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js +79 -0
- package/dist/schemas/mdast/MdastFromMarkdown.js.map +1 -0
- package/dist/schemas/mdast/MdastSchema.d.ts +385 -0
- package/dist/schemas/mdast/MdastSchema.d.ts.map +1 -0
- package/dist/schemas/mdast/MdastSchema.js +266 -0
- package/dist/schemas/mdast/MdastSchema.js.map +1 -0
- package/dist/schemas/mdast/index.d.ts +10 -0
- package/dist/schemas/mdast/index.d.ts.map +1 -0
- package/dist/schemas/mdast/index.js +4 -0
- package/dist/schemas/mdast/index.js.map +1 -0
- package/dist/schemas/mdast/mdastToString.d.ts +13 -0
- package/dist/schemas/mdast/mdastToString.d.ts.map +1 -0
- package/dist/schemas/mdast/mdastToString.js +85 -0
- package/dist/schemas/mdast/mdastToString.js.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts +43 -0
- package/dist/schemas/nodes/block/BlockSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/block/BlockSchema.js +634 -0
- package/dist/schemas/nodes/block/BlockSchema.js.map +1 -0
- package/dist/schemas/nodes/block/index.d.ts +7 -0
- package/dist/schemas/nodes/block/index.d.ts.map +1 -0
- package/dist/schemas/nodes/block/index.js +7 -0
- package/dist/schemas/nodes/block/index.js.map +1 -0
- package/dist/schemas/nodes/index.d.ts +9 -0
- package/dist/schemas/nodes/index.d.ts.map +1 -0
- package/dist/schemas/nodes/index.js +12 -0
- package/dist/schemas/nodes/index.js.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts +48 -0
- package/dist/schemas/nodes/inline/InlineSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/InlineSchema.js +436 -0
- package/dist/schemas/nodes/inline/InlineSchema.js.map +1 -0
- package/dist/schemas/nodes/inline/index.d.ts +7 -0
- package/dist/schemas/nodes/inline/index.d.ts.map +1 -0
- package/dist/schemas/nodes/inline/index.js +7 -0
- package/dist/schemas/nodes/inline/index.js.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts +27 -0
- package/dist/schemas/nodes/macro/MacroSchema.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/MacroSchema.js +162 -0
- package/dist/schemas/nodes/macro/MacroSchema.js.map +1 -0
- package/dist/schemas/nodes/macro/index.d.ts +7 -0
- package/dist/schemas/nodes/macro/index.d.ts.map +1 -0
- package/dist/schemas/nodes/macro/index.js +7 -0
- package/dist/schemas/nodes/macro/index.js.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts +24 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.d.ts.map +1 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js +351 -0
- package/dist/schemas/preprocessing/ConfluencePreprocessor.js.map +1 -0
- package/dist/schemas/preprocessing/index.d.ts +8 -0
- package/dist/schemas/preprocessing/index.d.ts.map +1 -0
- package/dist/schemas/preprocessing/index.js +2 -0
- package/dist/schemas/preprocessing/index.js.map +1 -0
- package/dist/serializers/ConfluenceSerializer.d.ts +30 -0
- package/dist/serializers/ConfluenceSerializer.d.ts.map +1 -0
- package/dist/serializers/ConfluenceSerializer.js +551 -0
- package/dist/serializers/ConfluenceSerializer.js.map +1 -0
- package/dist/serializers/MarkdownSerializer.d.ts +34 -0
- package/dist/serializers/MarkdownSerializer.d.ts.map +1 -0
- package/dist/serializers/MarkdownSerializer.js +355 -0
- package/dist/serializers/MarkdownSerializer.js.map +1 -0
- package/dist/serializers/index.d.ts +8 -0
- package/dist/serializers/index.d.ts.map +1 -0
- package/dist/serializers/index.js +8 -0
- package/dist/serializers/index.js.map +1 -0
- package/package.json +32 -21
- package/src/ConfluenceAuth.ts +581 -0
- package/src/ConfluenceClient.ts +230 -165
- package/src/ConfluenceConfig.ts +63 -7
- package/src/ConfluenceError.ts +110 -14
- package/src/GitError.ts +92 -0
- package/src/GitService.ts +859 -0
- package/src/LocalFileSystem.ts +179 -9
- package/src/MarkdownConverter.ts +126 -122
- package/src/SchemaConverterError.ts +108 -0
- package/src/Schemas.ts +223 -6
- package/src/SyncEngine.ts +745 -162
- package/src/ast/BlockNode.ts +425 -0
- package/src/ast/Document.ts +90 -0
- package/src/ast/InlineNode.ts +323 -0
- package/src/ast/MacroNode.ts +245 -0
- package/src/ast/index.ts +83 -0
- package/src/bin.ts +50 -249
- package/src/commands/auth.ts +117 -0
- package/src/commands/clone.ts +145 -0
- package/src/commands/delete.ts +57 -0
- package/src/commands/errorHandler.ts +32 -0
- package/src/commands/git.ts +114 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/layers.ts +211 -0
- package/src/commands/new.ts +99 -0
- package/src/commands/pageTree.ts +40 -0
- package/src/commands/shared.ts +35 -0
- package/src/commands/sync.ts +129 -0
- package/src/index.ts +21 -1
- package/src/internal/NodeLayers.ts +21 -0
- package/src/internal/frontmatter.ts +21 -0
- package/src/internal/gitCommands.ts +229 -0
- package/src/internal/hashUtils.ts +65 -3
- package/src/internal/oauthServer.ts +199 -0
- package/src/internal/pathUtils.ts +34 -17
- package/src/internal/tokenStorage.ts +240 -0
- package/src/internal/userCache.ts +90 -0
- package/src/parsers/ConfluenceParser.ts +950 -0
- package/src/parsers/MarkdownParser.ts +1198 -0
- package/src/parsers/index.ts +8 -0
- package/src/schemas/ConfluenceSchema.ts +56 -0
- package/src/schemas/ConversionSchema.ts +318 -0
- package/src/schemas/MarkdownSchema.ts +56 -0
- package/src/schemas/hast/HastFromHtml.ts +153 -0
- package/src/schemas/hast/HastSchema.ts +274 -0
- package/src/schemas/hast/index.ts +35 -0
- package/src/schemas/index.ts +20 -0
- package/src/schemas/mdast/MdastFromMarkdown.ts +118 -0
- package/src/schemas/mdast/MdastSchema.ts +566 -0
- package/src/schemas/mdast/index.ts +59 -0
- package/src/schemas/mdast/mdastToString.ts +102 -0
- package/src/schemas/nodes/block/BlockSchema.ts +773 -0
- package/src/schemas/nodes/block/index.ts +13 -0
- package/src/schemas/nodes/index.ts +20 -0
- package/src/schemas/nodes/inline/InlineSchema.ts +523 -0
- package/src/schemas/nodes/inline/index.ts +14 -0
- package/src/schemas/nodes/macro/MacroSchema.ts +226 -0
- package/src/schemas/nodes/macro/index.ts +6 -0
- package/src/schemas/preprocessing/ConfluencePreprocessor.ts +446 -0
- package/src/schemas/preprocessing/index.ts +8 -0
- package/src/serializers/ConfluenceSerializer.ts +717 -0
- package/src/serializers/MarkdownSerializer.ts +493 -0
- package/src/serializers/index.ts +8 -0
- package/test/GitService.test.ts +209 -0
- package/test/MarkdownConverter.test.ts +37 -3
- package/test/Schemas.test.ts +97 -2
- package/test/ast/BlockNode.test.ts +265 -0
- package/test/ast/Document.test.ts +126 -0
- package/test/ast/InlineNode.test.ts +161 -0
- package/test/fixtures/integration-test.html.fixture +103 -0
- package/test/fixtures/integration-test.md.expected +257 -0
- package/test/integration.test.ts +269 -0
- package/test/oauthServer.test.ts +50 -0
- package/test/parsers/ConfluenceParser.test.ts +283 -0
- package/test/schemas/ConfluencePreprocessor.test.ts +180 -0
- package/test/schemas/ConversionSchema.test.ts +159 -0
- package/test/schemas/HastSchema.test.ts +138 -0
- package/test/schemas/MdastSchema.test.ts +145 -0
- package/test/schemas/nodes/block/BlockSchema.test.ts +173 -0
- package/test/schemas/nodes/inline/InlineSchema.test.ts +198 -0
- package/test/schemas/nodes/macro/MacroSchema.test.ts +142 -0
- 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
|
+
})
|