@xyd-js/content 0.1.0-xyd.13 → 0.1.0-xyd.16

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 (89) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/ISSUES.md +1 -0
  3. package/TODO.md +2 -0
  4. package/dist/index.d.ts +24 -5
  5. package/dist/index.js +1411 -21776
  6. package/dist/index.js.map +1 -1
  7. package/dist/md.d.ts +62 -7
  8. package/dist/md.js +18071 -15085
  9. package/dist/md.js.map +1 -1
  10. package/dist/{mdToc-CYxzibVZ.d.ts → mdToc-NBBxMJ4l.d.ts} +1 -0
  11. package/dist/vite.d.ts +81 -2
  12. package/dist/vite.js +9716 -10090
  13. package/dist/vite.js.map +1 -1
  14. package/example.txt +0 -0
  15. package/package.json +24 -6
  16. package/packages/md/index.ts +17 -8
  17. package/packages/md/plugins/component-directives/index.ts +3 -0
  18. package/packages/md/plugins/component-directives/mdComponentDirective.ts +524 -0
  19. package/packages/md/plugins/component-directives/types.ts +1 -0
  20. package/packages/md/plugins/component-directives/utils.ts +27 -0
  21. package/packages/md/plugins/composer/__fixtures__/1.single-example/input.md +7 -0
  22. package/packages/md/plugins/composer/__fixtures__/1.single-example/output.json +63 -0
  23. package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/input.md +7 -0
  24. package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/output.json +63 -0
  25. package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/input.md +15 -0
  26. package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/output.json +122 -0
  27. package/packages/md/plugins/composer/__fixtures__/4.example-groups/input.md +23 -0
  28. package/packages/md/plugins/composer/__fixtures__/4.example-groups/output.json +184 -0
  29. package/packages/md/plugins/composer/__tests__/mdComposer.test.ts +41 -0
  30. package/packages/md/plugins/composer/__tests__/testHelpers.ts +48 -0
  31. package/packages/md/plugins/composer/index.ts +1 -0
  32. package/packages/md/plugins/composer/mdComposer.ts +146 -0
  33. package/packages/md/plugins/developer-writing/index.ts +3 -0
  34. package/packages/md/plugins/developer-writing/mdCodeRehype.ts +78 -0
  35. package/packages/md/plugins/functions/__fixtures__/external.ts +4 -0
  36. package/packages/md/plugins/functions/__fixtures__/test.js +11 -0
  37. package/packages/md/plugins/functions/__fixtures__/test.py +9 -0
  38. package/packages/md/plugins/functions/__fixtures__/test.ts +18 -0
  39. package/packages/md/plugins/functions/__tests__/mdFunctionImportCode.test.ts +295 -0
  40. package/packages/md/plugins/functions/__tests__/parseFunctionCall.test.ts +47 -0
  41. package/packages/md/plugins/functions/__tests__/testHelpers.ts +71 -0
  42. package/packages/md/plugins/functions/index.ts +11 -0
  43. package/packages/md/plugins/functions/mdFunctionChangelog.ts +124 -0
  44. package/packages/md/plugins/functions/mdFunctionImportCode.ts +83 -0
  45. package/packages/md/plugins/functions/mdFunctionUniform.ts +79 -0
  46. package/packages/md/plugins/functions/types.ts +6 -0
  47. package/packages/md/plugins/functions/uniformProcessor.ts +349 -0
  48. package/packages/md/plugins/functions/utils.ts +423 -0
  49. package/packages/md/plugins/index.ts +56 -11
  50. package/packages/md/plugins/mdCode.ts +52 -4
  51. package/packages/md/plugins/mdHeadingId.ts +47 -0
  52. package/packages/md/plugins/mdPage.ts +3 -0
  53. package/packages/md/plugins/mdThemeSettings.ts +4 -0
  54. package/packages/md/plugins/mdToc.ts +108 -17
  55. package/packages/md/plugins/meta/index.ts +1 -0
  56. package/packages/md/plugins/meta/mdMeta.ts +189 -0
  57. package/packages/md/plugins/output-variables/__fixtures__/1.simple/input.md +22 -0
  58. package/packages/md/plugins/output-variables/__fixtures__/1.simple/output.json +191 -0
  59. package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/input.md +21 -0
  60. package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/output.json +127 -0
  61. package/packages/md/plugins/output-variables/__tests__/index.test.ts +28 -0
  62. package/packages/md/plugins/output-variables/__tests__/testHelpers.ts +36 -0
  63. package/packages/md/plugins/output-variables/index.ts +1 -0
  64. package/packages/md/plugins/output-variables/lib/const.ts +4 -0
  65. package/packages/md/plugins/output-variables/lib/factoryAttributes.ts +350 -0
  66. package/packages/md/plugins/output-variables/lib/factoryLabel.ts +135 -0
  67. package/packages/md/plugins/output-variables/lib/factoryName.ts +59 -0
  68. package/packages/md/plugins/output-variables/lib/index.ts +21 -0
  69. package/packages/md/plugins/output-variables/lib/outputVarsContainer.ts +328 -0
  70. package/packages/md/plugins/output-variables/lib/util.ts +494 -0
  71. package/packages/md/plugins/output-variables/remarkOutputVars.ts +22 -0
  72. package/packages/md/plugins/rehypeHeading.ts +50 -0
  73. package/packages/md/plugins/types.ts +15 -0
  74. package/packages/md/plugins/utils/componentLike.ts +72 -0
  75. package/packages/md/plugins/utils/index.ts +2 -0
  76. package/packages/md/plugins/utils/mdParameters.test.ts +114 -0
  77. package/packages/md/plugins/utils/mdParameters.ts +249 -0
  78. package/packages/md/plugins/utils/mdastTypes.ts +42 -0
  79. package/packages/md/search/index.ts +251 -0
  80. package/packages/md/search/types.ts +36 -0
  81. package/packages/vite/index.ts +8 -2
  82. package/src/fs.ts +51 -36
  83. package/src/index.ts +4 -4
  84. package/src/navigation.ts +50 -38
  85. package/src/types.ts +8 -0
  86. package/tsconfig.json +31 -8
  87. package/vitest.config.ts +17 -0
  88. package/packages/md/plugins/mdCodeGroup.ts +0 -40
  89. package/packages/md/plugins/mdComponentDirective.ts +0 -141
@@ -0,0 +1,146 @@
1
+ import { Plugin } from "unified";
2
+ import { visit } from "unist-util-visit";
3
+ import { Node as UnistNode } from "unist";
4
+ import { highlight } from "codehike/code";
5
+ import type { HighlightedCode, Token } from "codehike/code";
6
+
7
+ import { Settings } from "@xyd-js/core";
8
+
9
+ import { SymbolxVfile } from "../types";
10
+
11
+ type Whitespace = string;
12
+
13
+ // Define types for our examples structure
14
+ export interface ExampleCode {
15
+ language: string;
16
+ title?: string;
17
+ tokens: (Token | Whitespace)[];
18
+ }
19
+
20
+ // TODO: ISSUES IF MULTIPLE OUTPUT VARS - PROBLEMS WITH ASYNC + currentQueue/currentVarName
21
+ // Main plugin function
22
+ export function mdComposer(settings?: Settings): Plugin {
23
+ return () => async (tree: UnistNode, file: SymbolxVfile<any>) => {
24
+ console.time('plugin:mdComposer');
25
+ const varQueue: Record<string, any> = {}
26
+
27
+ // Create a context object to store the current state for each variable
28
+ const contexts: Record<string, {
29
+ queue: (HighlightedCode | any[])[];
30
+ promises: Promise<any>[];
31
+ }> = {}
32
+
33
+ visit(tree, "outputVars", (node: any) => {
34
+ const varName = node.name
35
+ varQueue[varName] = []
36
+ contexts[varName] = {
37
+ queue: varQueue[varName],
38
+ promises: []
39
+ }
40
+
41
+ throughNodes(node, contexts[varName])
42
+ })
43
+
44
+ visit(tree, 'containerDirective', (node: any) => {
45
+ if (node.name !== "code-group") {
46
+ return
47
+ }
48
+
49
+ // Find the most recently created context
50
+ const lastVarName = Object.keys(contexts).pop()
51
+ if (!lastVarName) return
52
+
53
+ const context = contexts[lastVarName]
54
+ const group = [node.attributes.title]
55
+ context.queue.push(group)
56
+
57
+ if (node.data?.hName === "DirectiveCodeGroup") {
58
+ const codeblocksJSON = node.data.hProperties.codeblocks
59
+
60
+ if (codeblocksJSON) {
61
+ const codeblocks = JSON.parse(codeblocksJSON)
62
+
63
+ if (!codeblocks || !codeblocks.length) {
64
+ return
65
+ }
66
+
67
+ for (const codeblock of codeblocks) {
68
+ group.push(codeblock)
69
+ }
70
+ }
71
+
72
+ return
73
+ }
74
+
75
+ for (const child of node.children) {
76
+ if (child.type !== 'code') {
77
+ continue
78
+ }
79
+
80
+ highlightCode(child, group, context)
81
+ }
82
+ });
83
+
84
+ async function highlightCode(node: any, group?: any[], context?: typeof contexts[string]) {
85
+ const highlighted = await highlight({
86
+ value: node.value,
87
+ lang: node.lang,
88
+ meta: node.meta,
89
+ }, settings?.theme?.coder?.syntaxHighlight || "github-dark")
90
+
91
+ if (group && context) {
92
+ group.push(highlighted)
93
+ return
94
+ }
95
+
96
+ if (context) {
97
+ context.queue.push(highlighted)
98
+ }
99
+ }
100
+
101
+ function nodeCode(node: any, context: typeof contexts[string]) {
102
+ context.promises.push(highlightCode(node, undefined, context))
103
+ }
104
+
105
+ function nodeList(node: any, context: typeof contexts[string]) {
106
+ for (const item of node.children) {
107
+ if (item.type != "listItem") {
108
+ continue
109
+ }
110
+
111
+ throughNodes(item, context)
112
+ }
113
+ }
114
+
115
+ function throughNodes(node: any, context: typeof contexts[string]) {
116
+ for (const child of node.children) {
117
+ switch (child.type) {
118
+ case "code": {
119
+ nodeCode(child, context)
120
+ break
121
+ }
122
+ case "list": {
123
+ nodeList(child, context)
124
+ break
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ // Wait for all promises from all contexts to resolve
131
+ await Promise.all(Object.values(contexts).flatMap(ctx => ctx.promises))
132
+ console.timeEnd('plugin:mdComposer');
133
+
134
+ // Process the results to create the final output structure
135
+ const outputVars = {}
136
+
137
+ for (const [varName, value] of Object.entries(varQueue)) {
138
+ outputVars[varName] = value
139
+ }
140
+
141
+ file.data.outputVars = {
142
+ ...(file.data.outputVars || {}),
143
+ ...outputVars
144
+ }
145
+ };
146
+ }
@@ -0,0 +1,3 @@
1
+ export {
2
+ mdCodeRehype
3
+ } from "./mdCodeRehype"
@@ -0,0 +1,78 @@
1
+ import { visit } from "unist-util-visit";
2
+ import { highlight } from "codehike/code"
3
+
4
+ import { Settings } from "@xyd-js/core";
5
+ import { ContentFS } from "../../../../src";
6
+ import { defaultRemarkPlugins } from "..";
7
+
8
+ export function mdCodeRehype(settings?: Settings) {
9
+ const maxTocDepth = settings?.theme?.maxTocDepth || 2
10
+ const remarkPlugins = [...defaultRemarkPlugins({
11
+ maxDepth: maxTocDepth,
12
+ }, settings)]
13
+
14
+ const contentFs = new ContentFS(settings || {}, remarkPlugins, [])
15
+
16
+ return function () {
17
+ return async (tree: any) => {
18
+ console.time('plugin:mdServerHighlight');
19
+ const promises: Promise<any>[] = []
20
+
21
+ visit(tree, 'element', (node) => {
22
+ if (node.tagName === 'pre') {
23
+ const code = node.children[0].children[0].value
24
+ const lang = node.children?.[0]?.properties?.className?.[0]?.replace("language-", "")
25
+
26
+ // const regions = node.children?.[0]?.properties?.regions // TODO: in the future
27
+ const lineRanges = (node.children?.[0]?.properties?.lineRanges
28
+ ? JSON.parse(node.children?.[0]?.properties?.lineRanges)
29
+ : []) as { start?: number, end?: number }[]
30
+
31
+ const lineNumbers = node.children?.[0]?.properties?.lineNumbers
32
+ const size = node.children?.[0]?.properties?.size
33
+ const descriptionHead = node.children?.[0]?.properties?.descriptionHead
34
+ const descriptionContent = node.children?.[0]?.properties?.descriptionContent
35
+ const descriptionIcon = node.children?.[0]?.properties?.descriptionIcon
36
+
37
+ const promise = (async () => {
38
+ let descriptionContentCode = ""
39
+
40
+ if (descriptionContent) {
41
+ descriptionContentCode = await contentFs.compileContent(descriptionContent)
42
+ }
43
+
44
+ const highlighted = await highlight({
45
+ value: code,
46
+ lang: lang,
47
+ meta: lang || "",
48
+ }, settings?.theme?.coder?.syntaxHighlight || "github-dark")
49
+
50
+ if (lineRanges && lineRanges.length) {
51
+ highlighted.annotations = lineRanges.map((range) => ({
52
+ name: "mark",
53
+ query: "",
54
+ fromLineNumber: range.start || 0,
55
+ toLineNumber: range.end || 0,
56
+ }))
57
+ }
58
+
59
+ node.properties = {
60
+ ...node.properties,
61
+ highlighted: JSON.stringify(highlighted),
62
+ lineNumbers,
63
+ size,
64
+ descriptionHead,
65
+ descriptionContent: descriptionContentCode,
66
+ descriptionIcon
67
+ };
68
+ })()
69
+
70
+ promises.push(promise)
71
+ }
72
+ });
73
+
74
+ await Promise.all(promises)
75
+ console.timeEnd('plugin:mdServerHighlight');
76
+ };
77
+ }
78
+ }
@@ -0,0 +1,4 @@
1
+ // This is a mock external file for testing
2
+ export function externalFunction() {
3
+ return "Hello from external";
4
+ }
@@ -0,0 +1,11 @@
1
+ // This is a JavaScript test file
2
+ export function testFunction() {
3
+ return 'Hello, world!';
4
+ }
5
+
6
+ // This is a region for testing
7
+ // #region testRegion
8
+ export function regionFunction() {
9
+ return 'This is from a region';
10
+ }
11
+ // #endregion testRegion
@@ -0,0 +1,9 @@
1
+ # This is a Python test file
2
+ def test_function():
3
+ return "Hello, world!"
4
+
5
+ # This is a region for testing
6
+ # #region testRegion
7
+ def region_function():
8
+ return "This is from a region"
9
+ # #endregion testRegion
@@ -0,0 +1,18 @@
1
+ // This is a test file for the mdFunctionImport plugin
2
+ export function testFunction(): string {
3
+ return 'Hello, world!';
4
+ }
5
+
6
+ // This is a region for testing
7
+ // #region testRegion
8
+ export function regionFunction(): string {
9
+ return 'This is from a region';
10
+ }
11
+ // #endregion testRegion
12
+
13
+ // This is another region for testing
14
+ // #region anotherRegion
15
+ export function anotherRegionFunction(): string {
16
+ return 'This is from another region';
17
+ }
18
+ // #endregion anotherRegion
@@ -0,0 +1,295 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+
4
+ import { describe, it, expect } from 'vitest';
5
+
6
+ import { mdFunctionImportCode } from '../mdFunctionImportCode';
7
+ import { parseImportPath, processContent, detectLanguage } from '../utils';
8
+
9
+ import { createMockTree, createMockFile, createMockFetch, restoreFetch, createMockTreeAlternativeSyntax } from './testHelpers';
10
+
11
+ // Get the absolute path to the fixtures directory
12
+ const fixturesDir = path.resolve(__dirname, '../__fixtures__');
13
+
14
+ describe('mdFunctionImportCode', () => {
15
+ describe('parseImportPath', () => {
16
+ it('should parse a simple file path', () => {
17
+ const result = parseImportPath('path/to/file.ts');
18
+ expect(result).toEqual({
19
+ filePath: 'path/to/file.ts',
20
+ regions: [],
21
+ lineRanges: []
22
+ });
23
+ });
24
+
25
+ it('should parse a file path with regions', () => {
26
+ const result = parseImportPath('path/to/file.ts#region1,region2');
27
+ expect(result).toEqual({
28
+ filePath: 'path/to/file.ts',
29
+ regions: [
30
+ { name: 'region1' },
31
+ { name: 'region2' }
32
+ ],
33
+ lineRanges: []
34
+ });
35
+ });
36
+
37
+
38
+ it('relative parent folder', () => {
39
+ const result = parseImportPath('../relative/parents#Theme');
40
+ expect(result).toEqual({
41
+ filePath: '../relative/parents',
42
+ regions: [
43
+ { name: 'Theme' }
44
+ ],
45
+ lineRanges: []
46
+ });
47
+ });
48
+
49
+ it('should handle curly braces in region names for openapi like', () => {
50
+ const result = parseImportPath('~/api/rest/openapi.yaml#PUT /alerts/{id}');
51
+ expect(result).toEqual({
52
+ filePath: '~/api/rest/openapi.yaml',
53
+ regions: [
54
+ { name: 'PUT /alerts/{id}' }
55
+ ],
56
+ lineRanges: []
57
+ });
58
+ });
59
+
60
+ it('should parse a file path with line ranges', () => {
61
+ const result = parseImportPath('path/to/file.ts{1,2-4,8:,:10}');
62
+ expect(result).toEqual({
63
+ filePath: 'path/to/file.ts',
64
+ regions: [],
65
+ lineRanges: [
66
+ { start: 1, end: 1 },
67
+ { start: 2, end: 4 },
68
+ { start: 8 },
69
+ { end: 10 }
70
+ ]
71
+ });
72
+ });
73
+
74
+ it('should parse a file path with both regions and line ranges', () => {
75
+ const result = parseImportPath('path/to/file.ts#region1{1-5}');
76
+ expect(result).toEqual({
77
+ filePath: 'path/to/file.ts',
78
+ regions: [
79
+ { name: 'region1' }
80
+ ],
81
+ lineRanges: [
82
+ { start: 1, end: 5 }
83
+ ]
84
+ });
85
+ });
86
+ });
87
+
88
+ describe('processContent', () => {
89
+ const sampleContent = `line1
90
+ // #region test
91
+ line2
92
+ line3
93
+ // #endregion test
94
+ line4
95
+ line5
96
+ // #region another
97
+ line6
98
+ // #endregion another
99
+ line7
100
+ line8
101
+ line9
102
+ `;
103
+
104
+ it('should return the original content when no regions or line ranges are specified', () => {
105
+ const result = processContent(sampleContent, [], []);
106
+ expect(result).toBe(sampleContent);
107
+ });
108
+
109
+ it('should extract content from specified regions', () => {
110
+ const result = processContent(sampleContent, [{ name: 'test' }], []);
111
+ expect(result).toBe('line2\nline3');
112
+ });
113
+
114
+ it('should extract content from multiple regions', () => {
115
+ const result = processContent(sampleContent, [{ name: 'test' }, { name: 'another' }], []);
116
+ expect(result).toBe('line2\nline3\nline6');
117
+ });
118
+
119
+ it('should extract content based on line ranges', () => {
120
+ const result = processContent(sampleContent, [], [{ start: 3, end: 4 }]);
121
+ expect(result).toBe('line2\nline3');
122
+ });
123
+
124
+ it('should handle multiple line ranges', () => {
125
+ const result = processContent(sampleContent, [], [
126
+ { start: 1, end: 1 },
127
+ { start: 7, end: 7 }
128
+ ]);
129
+ expect(result).toBe('line1\nline5');
130
+ });
131
+
132
+ it('should handle open-ended ranges', () => {
133
+ const result = processContent(sampleContent, [], [{ start: 11 }]);
134
+ expect(result).toBe('line7\nline8\nline9\n');
135
+ });
136
+
137
+ it('should handle ranges from start', () => {
138
+ const result = processContent(sampleContent, [], [{ end: 1 }]);
139
+ expect(result).toBe('line1');
140
+ });
141
+ });
142
+
143
+ describe('detectLanguage', () => {
144
+ it('should detect JavaScript language', () => {
145
+ expect(detectLanguage('file.js')).toBe('javascript');
146
+ });
147
+
148
+ it('should detect TypeScript language', () => {
149
+ expect(detectLanguage('file.ts')).toBe('typescript');
150
+ });
151
+
152
+ it('should detect Python language', () => {
153
+ expect(detectLanguage('file.py')).toBe('python');
154
+ });
155
+
156
+ it('should return empty string for unknown extension', () => {
157
+ expect(detectLanguage('file.xyz')).toBe('xyz');
158
+ });
159
+
160
+ it('should handle uppercase extensions', () => {
161
+ expect(detectLanguage('file.TS')).toBe('typescript');
162
+ });
163
+ });
164
+
165
+ describe('transformer', () => {
166
+ it('should transform @importCode statements to code blocks', async () => {
167
+ const transformer = mdFunctionImportCode()();
168
+ const tree = createMockTree('test.ts');
169
+ const file = createMockFile();
170
+
171
+ await transformer(tree, file);
172
+
173
+ expect(tree.children[0].type).toBe('code');
174
+ expect((tree.children[0] as any).lang).toBe('typescript');
175
+ expect((tree.children[0] as any).value).toContain('export function testFunction');
176
+ });
177
+
178
+ it('should handle non-existent files gracefully', async () => {
179
+ const transformer = mdFunctionImportCode()();
180
+ const tree = createMockTree('nonexistent.ts');
181
+ const file = createMockFile();
182
+
183
+ // Mock console.error to prevent test output pollution
184
+ const originalConsoleError = console.error;
185
+ console.error = () => {
186
+ };
187
+
188
+ await transformer(tree, file);
189
+
190
+ // Restore console.error
191
+ console.error = originalConsoleError;
192
+
193
+ // The node should remain unchanged
194
+ expect(tree.children[0].type).toBe('paragraph');
195
+ });
196
+
197
+ it('should handle custom resolveFrom option', async () => {
198
+ const customFixturesDir = path.resolve(__dirname, '../__fixtures__');
199
+ const transformer = mdFunctionImportCode()({ resolveFrom: customFixturesDir });
200
+ const tree = createMockTree('test.ts');
201
+ const file = createMockFile('/some/other/directory');
202
+
203
+ await transformer(tree, file);
204
+
205
+ expect(tree.children[0].type).toBe('code');
206
+ expect((tree.children[0] as any).lang).toBe('typescript');
207
+ expect((tree.children[0] as any).value).toContain('export function testFunction');
208
+ });
209
+
210
+ it('should handle complex import paths with regions and line ranges', async () => {
211
+ const transformer = mdFunctionImportCode()()
212
+ const tree = createMockTree('test.ts#testRegion{1-3}');
213
+ const file = createMockFile();
214
+
215
+ await transformer(tree, file);
216
+
217
+ expect(tree.children[0].type).toBe('code');
218
+ expect((tree.children[0] as any).lang).toBe('typescript');
219
+ expect((tree.children[0] as any).value).toContain('export function regionFunction');
220
+ });
221
+
222
+ it('should handle import paths with only regions', async () => {
223
+ const transformer = mdFunctionImportCode()()
224
+ const tree = createMockTree('test.ts#testRegion');
225
+ const file = createMockFile();
226
+
227
+ await transformer(tree, file);
228
+
229
+ expect(tree.children[0].type).toBe('code');
230
+ expect((tree.children[0] as any).lang).toBe('typescript');
231
+ expect((tree.children[0] as any).value).toContain('export function regionFunction');
232
+ });
233
+
234
+ it('should handle import paths with only line ranges', async () => {
235
+ const transformer = mdFunctionImportCode()()
236
+ const tree = createMockTree('test.ts{1-3}');
237
+ const file = createMockFile();
238
+
239
+ await transformer(tree, file);
240
+
241
+ expect(tree.children[0].type).toBe('code');
242
+ expect((tree.children[0] as any).lang).toBe('typescript');
243
+ expect((tree.children[0] as any).value).toContain('export function testFunction');
244
+ });
245
+
246
+ it('should handle external URLs', async () => {
247
+ // Create mock fetch with our fixture content
248
+ const externalUrl = 'https://example.com/test.ts';
249
+ const fixtureContent = fs.readFileSync(path.resolve(fixturesDir, 'external.ts'), 'utf8');
250
+ const originalFetch = createMockFetch({
251
+ [externalUrl]: fixtureContent
252
+ });
253
+
254
+ const transformer = mdFunctionImportCode()()
255
+ const tree = createMockTree(externalUrl);
256
+ const file = createMockFile();
257
+
258
+ await transformer(tree, file);
259
+
260
+ // Restore original fetch
261
+ restoreFetch(originalFetch);
262
+
263
+ expect(tree.children[0].type).toBe('code');
264
+ expect((tree.children[0] as any).lang).toBe('typescript');
265
+ expect((tree.children[0] as any).value).toContain('export function externalFunction');
266
+ });
267
+
268
+ it('should debug visit function', async () => {
269
+ const transformer = mdFunctionImportCode()()
270
+ const tree = createMockTree('test.ts');
271
+
272
+ const file = createMockFile();
273
+
274
+ // Create a promise that resolves when the transformer is done
275
+ const transformPromise = transformer(tree, file);
276
+
277
+ // Wait for the promise to resolve
278
+ await transformPromise;
279
+
280
+ expect(tree.children[0].type).toBe('code');
281
+ });
282
+
283
+ it('should handle alternative syntax like @importCode("test.ts")', async () => {
284
+ const transformer = mdFunctionImportCode()()
285
+ const tree = createMockTreeAlternativeSyntax('test.ts');
286
+ const file = createMockFile();
287
+
288
+ await transformer(tree, file);
289
+
290
+ expect(tree.children[0].type).toBe('code');
291
+ expect((tree.children[0] as any).lang).toBe('typescript');
292
+ expect((tree.children[0] as any).value).toContain('export function testFunction');
293
+ });
294
+ });
295
+ });
@@ -0,0 +1,47 @@
1
+ import {describe, it, expect} from 'vitest';
2
+
3
+ import {parseFunctionCall} from '../utils';
4
+ import {FunctionName} from "../types";
5
+
6
+ describe('parseFunctionCall', () => {
7
+ const testCases = [
8
+ {
9
+ name: '1a. should parse uniform with file path and options',
10
+ input: `@uniform('~/path/to/resource.ts', {"mini": "Settings"})`,
11
+ expected: ['~/path/to/resource.ts', {mini: 'Settings'}]
12
+ },
13
+ {
14
+ name: '1b. should parse uniform with file path and options',
15
+ input: `@uniform('~/path/to/resource.ts', {mini: 'Settings'})`,
16
+ expected: ['~/path/to/resource.ts', {mini: 'Settings'}]
17
+ },
18
+ {
19
+ name: '2. should parse uniform with file path only',
20
+ input: '@uniform(~/path/to/resource.ts)',
21
+ expected: ['~/path/to/resource.ts']
22
+ },
23
+ {
24
+ name: '3a. should handle malformed uniform call',
25
+ input: '@uniform_invalid()',
26
+ expected: null
27
+ },
28
+ {
29
+ name: '3b. should handle malformed uniform call',
30
+ input: 'uniform()',
31
+ expected: null
32
+ }
33
+ ];
34
+
35
+ testCases.forEach(({name, input, expected}) => {
36
+ it(name, () => {
37
+ const node = {
38
+ children: [{
39
+ type: 'text',
40
+ value: input
41
+ }]
42
+ };
43
+ const result = parseFunctionCall(node, FunctionName.Uniform);
44
+ expect(result).toEqual(expected);
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,71 @@
1
+ import path from 'node:path';
2
+ import { VFile } from 'vfile';
3
+
4
+
5
+ export function createMockTree(importPath: string) {
6
+ return {
7
+ type: 'root',
8
+ children: [
9
+ {
10
+ type: 'paragraph',
11
+ children: [
12
+ {
13
+ type: 'text',
14
+ value: `@importCode "${importPath}"`
15
+ }
16
+ ]
17
+ }
18
+ ]
19
+ };
20
+ }
21
+
22
+ export function createMockTreeAlternativeSyntax(importPath: string) {
23
+ return {
24
+ type: 'root',
25
+ children: [
26
+ {
27
+ type: 'paragraph',
28
+ children: [
29
+ {
30
+ type: 'text',
31
+ value: `@importCode("${importPath}")`
32
+ }
33
+ ]
34
+ }
35
+ ]
36
+ };
37
+ }
38
+
39
+
40
+ export function createMockFile(dirname?: string) {
41
+ return new VFile({
42
+ path: 'test.md',
43
+ value: '',
44
+ dirname: dirname || path.resolve(__dirname, '../__fixtures__')
45
+ });
46
+ }
47
+
48
+
49
+ export function createMockFetch(mockResponses: Record<string, string>) {
50
+ const originalFetch = global.fetch;
51
+
52
+ global.fetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
53
+ const url = input.toString();
54
+
55
+ if (mockResponses[url]) {
56
+ return {
57
+ ok: true,
58
+ text: async () => mockResponses[url]
59
+ } as Response;
60
+ }
61
+
62
+ throw new Error(`Unexpected URL: ${url}`);
63
+ }) as typeof fetch;
64
+
65
+ return originalFetch;
66
+ }
67
+
68
+
69
+ export function restoreFetch(originalFetch: typeof global.fetch) {
70
+ global.fetch = originalFetch;
71
+ }