@iress-oss/ids-mcp-server 0.0.1-dev.4 → 0.0.1

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 (125) hide show
  1. package/LICENSE +193 -0
  2. package/README.md +159 -29
  3. package/build/componentHandlers.js +205 -0
  4. package/{dist → build}/config.js +5 -5
  5. package/build/index.js +51 -0
  6. package/{dist → build}/iressHandlers.js +46 -52
  7. package/{dist → build}/resourceHandlers.js +22 -23
  8. package/{dist → build}/searchHandlers.js +92 -107
  9. package/{dist → build}/toolHandler.js +13 -13
  10. package/build/tools.js +165 -0
  11. package/{dist → build}/utils.js +15 -11
  12. package/docs/api-reference.md +0 -0
  13. package/docs/best-practices.md +0 -0
  14. package/docs/configuration.md +0 -0
  15. package/docs/examples.md +0 -0
  16. package/docs/guidelines.md +269 -0
  17. package/{generated/docs → docs/ids}/components-autocomplete-docs.md +5 -5
  18. package/{generated/docs → docs/ids}/components-autocomplete-recipes-docs.md +51 -17
  19. package/{generated/docs → docs/ids}/components-card-recipes-docs.md +1 -1
  20. package/{generated/docs → docs/ids}/components-checkbox-docs.md +19 -6
  21. package/{generated/docs → docs/ids}/components-checkboxgroup-docs.md +18 -18
  22. package/{generated/docs → docs/ids}/components-checkboxgroup-recipes-docs.md +9 -9
  23. package/{generated/docs → docs/ids}/components-col-docs.md +1 -1
  24. package/{generated/docs → docs/ids}/components-combobox-docs.md +6 -6
  25. package/{generated/docs → docs/ids}/components-container-docs.md +42 -8
  26. package/{generated/docs → docs/ids}/components-filter-docs.md +66 -13
  27. package/{generated/docs → docs/ids}/components-form-docs.md +368 -342
  28. package/{generated/docs → docs/ids}/components-form-recipes-docs.md +11 -202
  29. package/{generated/docs → docs/ids}/components-hide-docs.md +70 -16
  30. package/{generated/docs → docs/ids}/components-icon-docs.md +4 -4
  31. package/{generated/docs → docs/ids}/components-input-recipes-docs.md +2 -2
  32. package/{generated/docs → docs/ids}/components-inputcurrency-recipes-docs.md +40 -6
  33. package/{generated/docs → docs/ids}/components-modal-docs.md +113 -3
  34. package/docs/ids/components-popover-docs.md +4 -0
  35. package/{generated/docs → docs/ids}/components-radiogroup-docs.md +21 -21
  36. package/{generated/docs → docs/ids}/components-richselect-docs.md +111 -149
  37. package/{generated/docs → docs/ids}/components-row-docs.md +4 -4
  38. package/{generated/docs → docs/ids}/components-skeleton-docs.md +3 -3
  39. package/{generated/docs → docs/ids}/components-skeleton-recipes-docs.md +1 -1
  40. package/{generated/docs → docs/ids}/components-skiplink-docs.md +1 -1
  41. package/{generated/docs → docs/ids}/components-slideout-docs.md +113 -3
  42. package/docs/ids/components-table-ag-grid-docs.md +2694 -0
  43. package/{generated/docs → docs/ids}/components-table-docs.md +597 -92
  44. package/{generated/docs → docs/ids}/components-tabset-docs.md +2 -2
  45. package/{generated/docs → docs/ids}/components-tag-docs.md +1 -1
  46. package/{generated/docs → docs/ids}/components-toaster-docs.md +5 -56
  47. package/{generated/docs → docs/ids}/extensions-editor-docs.md +5 -5
  48. package/{generated/docs → docs/ids}/foundations-colours-docs.md +1 -1
  49. package/{generated/docs → docs/ids}/foundations-typography-docs.md +2 -7
  50. package/docs/ids/get-started-develop-docs.md +48 -0
  51. package/{generated/docs → docs/ids}/introduction-docs.md +4 -4
  52. package/{generated/docs → docs/ids}/patterns-loading-docs.md +2 -332
  53. package/docs/ids/resources-migration-guides-from-v4-to-v5-docs.md +639 -0
  54. package/docs/ids/themes-available-themes-docs.md +74 -0
  55. package/docs/ids/themes-tokens-docs.md +4580 -0
  56. package/docs/ids/versions-docs.md +27 -0
  57. package/docs/tutorials/basic-integration.md +0 -0
  58. package/package.json +15 -44
  59. package/LICENSE.txt +0 -201
  60. package/dist/componentHandlers.js +0 -241
  61. package/dist/componentHandlers.test.js +0 -380
  62. package/dist/index.js +0 -53
  63. package/dist/iressHandlers.test.js +0 -316
  64. package/dist/resourceHandlers.test.js +0 -352
  65. package/dist/searchHandlers.test.js +0 -524
  66. package/dist/toolHandler.test.js +0 -369
  67. package/dist/tools.js +0 -165
  68. package/dist/utils.test.js +0 -286
  69. package/generated/docs/components-popover-docs.md +0 -464
  70. package/generated/docs/components-provider-docs.md +0 -105
  71. package/generated/docs/components-table-ag-grid-docs.md +0 -1074
  72. package/generated/docs/foundations-accessibility-docs.md +0 -62
  73. package/generated/docs/foundations-consistency-docs.md +0 -52
  74. package/generated/docs/foundations-content-docs.md +0 -23
  75. package/generated/docs/foundations-introduction-docs.md +0 -17
  76. package/generated/docs/foundations-principles-docs.md +0 -70
  77. package/generated/docs/foundations-user-experience-docs.md +0 -63
  78. package/generated/docs/foundations-visual-design-docs.md +0 -46
  79. package/generated/docs/get-started-develop-docs.md +0 -209
  80. package/generated/docs/guidelines.md +0 -812
  81. package/generated/docs/resources-migration-guides-from-v4-to-v5-docs.md +0 -437
  82. package/generated/docs/themes-available-themes-docs.md +0 -66
  83. package/generated/docs/themes-tokens-docs.md +0 -1200
  84. package/generated/docs/versions-docs.md +0 -17
  85. /package/{dist → build}/types.js +0 -0
  86. /package/{generated/docs → docs/ids}/components-alert-docs.md +0 -0
  87. /package/{generated/docs → docs/ids}/components-badge-docs.md +0 -0
  88. /package/{generated/docs → docs/ids}/components-button-docs.md +0 -0
  89. /package/{generated/docs → docs/ids}/components-button-recipes-docs.md +0 -0
  90. /package/{generated/docs → docs/ids}/components-buttongroup-docs.md +0 -0
  91. /package/{generated/docs → docs/ids}/components-card-docs.md +0 -0
  92. /package/{generated/docs → docs/ids}/components-divider-docs.md +0 -0
  93. /package/{generated/docs → docs/ids}/components-expander-docs.md +0 -0
  94. /package/{generated/docs → docs/ids}/components-field-docs.md +0 -0
  95. /package/{generated/docs → docs/ids}/components-inline-docs.md +0 -0
  96. /package/{generated/docs → docs/ids}/components-input-docs.md +0 -0
  97. /package/{generated/docs → docs/ids}/components-inputcurrency-docs.md +0 -0
  98. /package/{generated/docs → docs/ids}/components-label-docs.md +0 -0
  99. /package/{generated/docs → docs/ids}/components-menu-docs.md +0 -0
  100. /package/{generated/docs → docs/ids}/components-menu-menuitem-docs.md +0 -0
  101. /package/{generated/docs → docs/ids}/components-navbar-docs.md +0 -0
  102. /package/{generated/docs → docs/ids}/components-navbar-recipes-docs.md +0 -0
  103. /package/{generated/docs → docs/ids}/components-panel-docs.md +0 -0
  104. /package/{generated/docs → docs/ids}/components-placeholder-docs.md +0 -0
  105. /package/{generated/docs → docs/ids}/components-popover-recipes-docs.md +0 -0
  106. /package/{generated/docs → docs/ids}/components-progress-docs.md +0 -0
  107. /package/{generated/docs → docs/ids}/components-radio-docs.md +0 -0
  108. /package/{generated/docs → docs/ids}/components-readonly-docs.md +0 -0
  109. /package/{generated/docs → docs/ids}/components-select-docs.md +0 -0
  110. /package/{generated/docs → docs/ids}/components-slider-docs.md +0 -0
  111. /package/{generated/docs → docs/ids}/components-spinner-docs.md +0 -0
  112. /package/{generated/docs → docs/ids}/components-stack-docs.md +0 -0
  113. /package/{generated/docs → docs/ids}/components-tabset-tab-docs.md +0 -0
  114. /package/{generated/docs → docs/ids}/components-text-docs.md +0 -0
  115. /package/{generated/docs → docs/ids}/components-toaster-toast-docs.md +0 -0
  116. /package/{generated/docs → docs/ids}/components-toggle-docs.md +0 -0
  117. /package/{generated/docs → docs/ids}/components-tooltip-docs.md +0 -0
  118. /package/{generated/docs → docs/ids}/components-validationmessage-docs.md +0 -0
  119. /package/{generated/docs → docs/ids}/contact-us-docs.md +0 -0
  120. /package/{generated/docs → docs/ids}/extensions-editor-recipes-docs.md +0 -0
  121. /package/{generated/docs → docs/ids}/frequently-asked-questions-docs.md +0 -0
  122. /package/{generated/docs → docs/ids}/get-started-using-storybook-docs.md +0 -0
  123. /package/{generated/docs → docs/ids}/resources-changelog-docs.md +0 -0
  124. /package/{generated/docs → docs/ids}/resources-code-katas-docs.md +0 -0
  125. /package/{generated/docs → docs/ids}/themes-introduction-docs.md +0 -0
@@ -1,316 +0,0 @@
1
- /**
2
- * Tests for Iress component handlers
3
- */
4
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
5
- import { handleGetIressComponentInfo, handleAnalyzeComponentMentions, } from './iressHandlers.js';
6
- import * as utils from './utils.js';
7
- // Mock the utils module
8
- vi.mock('./utils.js', () => ({
9
- mapIressComponentToFile: vi.fn(),
10
- extractIressComponents: vi.fn(),
11
- readFileContent: vi.fn(),
12
- }));
13
- // Mock the config module
14
- vi.mock('./config.js', () => ({
15
- DOCS_DIR: '/mocked/docs/path',
16
- }));
17
- const mockUtils = vi.mocked(utils);
18
- describe('iressHandlers', () => {
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
- });
22
- afterEach(() => {
23
- vi.restoreAllMocks();
24
- });
25
- describe('handleGetIressComponentInfo', () => {
26
- const mockComponentContent = `# IressButton Component
27
-
28
- ## Overview
29
- The IressButton component is a versatile button component for the Iress Design System.
30
-
31
- ## Props
32
- - \`variant\`: string - The button variant (primary, secondary, etc.)
33
- - \`size\`: string - The button size (small, medium, large)
34
- - \`disabled\`: boolean - Whether the button is disabled
35
-
36
- ## Examples
37
- \`\`\`jsx
38
- <IressButton variant="primary" size="medium">
39
- Click me
40
- </IressButton>
41
- \`\`\`
42
-
43
- ## API Reference
44
- Additional API documentation here.
45
- `;
46
- it('should return component information with examples and props by default', () => {
47
- const args = {
48
- component_name: 'IressButton',
49
- };
50
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
51
- mockUtils.readFileContent.mockReturnValue(mockComponentContent);
52
- const result = handleGetIressComponentInfo(args);
53
- expect(mockUtils.mapIressComponentToFile).toHaveBeenCalledWith('IressButton');
54
- expect(mockUtils.readFileContent).toHaveBeenCalledWith('/mocked/docs/path/components-button-docs.md');
55
- expect(result.content).toHaveLength(1);
56
- expect(result.content[0].type).toBe('text');
57
- expect(result.content[0].text).toContain('**IressButton Component Documentation**');
58
- expect(result.content[0].text).toContain('The IressButton component is a versatile button component');
59
- expect(result.content[0].text).toContain('**Props & API:**');
60
- expect(result.content[0].text).toContain('**Usage Examples:**');
61
- expect(result.content[0].text).toContain('<IressButton variant="primary"');
62
- });
63
- it('should include examples when include_examples is true', () => {
64
- const args = {
65
- component_name: 'IressButton',
66
- include_examples: true,
67
- include_props: false,
68
- };
69
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
70
- mockUtils.readFileContent.mockReturnValue(mockComponentContent);
71
- const result = handleGetIressComponentInfo(args);
72
- expect(result.content[0].text).toContain('**Usage Examples:**');
73
- expect(result.content[0].text).not.toContain('**Props & API:**');
74
- });
75
- it('should include props when include_props is true', () => {
76
- const args = {
77
- component_name: 'IressButton',
78
- include_examples: false,
79
- include_props: true,
80
- };
81
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
82
- mockUtils.readFileContent.mockReturnValue(mockComponentContent);
83
- const result = handleGetIressComponentInfo(args);
84
- expect(result.content[0].text).toContain('**Props & API:**');
85
- expect(result.content[0].text).not.toContain('**Usage Examples:**');
86
- });
87
- it('should exclude examples and props when both flags are false', () => {
88
- const args = {
89
- component_name: 'IressButton',
90
- include_examples: false,
91
- include_props: false,
92
- };
93
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
94
- mockUtils.readFileContent.mockReturnValue(mockComponentContent);
95
- const result = handleGetIressComponentInfo(args);
96
- expect(result.content[0].text).not.toContain('**Usage Examples:**');
97
- expect(result.content[0].text).not.toContain('**Props & API:**');
98
- expect(result.content[0].text).toContain('**IressButton Component Documentation**');
99
- });
100
- it('should return error message when component is not found', () => {
101
- const args = {
102
- component_name: 'IressNonExistent',
103
- };
104
- mockUtils.mapIressComponentToFile.mockReturnValue(null);
105
- const result = handleGetIressComponentInfo(args);
106
- expect(result.content).toHaveLength(1);
107
- expect(result.content[0].type).toBe('text');
108
- expect(result.content[0].text).toContain('Component "IressNonExistent" not found');
109
- expect(result.content[0].text).toContain("Make sure you're using the correct Iress component name");
110
- });
111
- it('should throw error when file reading fails', () => {
112
- const args = {
113
- component_name: 'IressButton',
114
- };
115
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
116
- mockUtils.readFileContent.mockImplementation(() => {
117
- throw new Error('File not accessible');
118
- });
119
- expect(() => handleGetIressComponentInfo(args)).toThrow('Failed to read component documentation: File not accessible');
120
- });
121
- it('should validate input parameters using zod schema', () => {
122
- const invalidArgs = {
123
- component_name: 123, // Should be string
124
- };
125
- expect(() => handleGetIressComponentInfo(invalidArgs)).toThrow();
126
- });
127
- it('should handle missing optional parameters with defaults', () => {
128
- const args = {
129
- component_name: 'IressButton',
130
- // include_examples and include_props not provided, should default to true
131
- };
132
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
133
- mockUtils.readFileContent.mockReturnValue(mockComponentContent);
134
- const result = handleGetIressComponentInfo(args);
135
- expect(result.content[0].text).toContain('**Usage Examples:**');
136
- expect(result.content[0].text).toContain('**Props & API:**');
137
- });
138
- it('should limit examples and prop sections to 3 items each', () => {
139
- const contentWithMultipleExamples = `# IressButton
140
-
141
- ## Overview
142
- Button component
143
-
144
- ## Props
145
- First prop section
146
- ## API
147
- Second API section
148
- ## Properties
149
- Third properties section
150
- ## Mode
151
- Fourth mode section
152
-
153
- \`\`\`jsx
154
- Example 1
155
- \`\`\`
156
-
157
- \`\`\`jsx
158
- Example 2
159
- \`\`\`
160
-
161
- \`\`\`jsx
162
- Example 3
163
- \`\`\`
164
-
165
- \`\`\`jsx
166
- Example 4
167
- \`\`\`
168
-
169
- <IressButton>Example 5</IressButton>
170
- `;
171
- const args = {
172
- component_name: 'IressButton',
173
- };
174
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
175
- mockUtils.readFileContent.mockReturnValue(contentWithMultipleExamples);
176
- const result = handleGetIressComponentInfo(args);
177
- // Should limit to 3 prop sections and 3 examples
178
- const propMatches = result.content[0].text.match(/First prop section|Second API section|Third properties section|Fourth mode section/g);
179
- const exampleMatches = result.content[0].text.match(/Example [1-5]/g);
180
- expect(propMatches?.length).toBeLessThanOrEqual(3);
181
- expect(exampleMatches?.length).toBeLessThanOrEqual(3);
182
- });
183
- });
184
- describe('handleAnalyzeComponentMentions', () => {
185
- it('should analyze text and find component mentions', () => {
186
- const args = {
187
- text: 'Use IressButton and IressInput components in your form.',
188
- detailed: false,
189
- };
190
- mockUtils.extractIressComponents.mockReturnValue([
191
- 'IressButton',
192
- 'IressInput',
193
- ]);
194
- // Mock the function to return files for both calls
195
- mockUtils.mapIressComponentToFile.mockImplementation((name) => {
196
- if (name === 'IressButton')
197
- return 'components-button-docs.md';
198
- if (name === 'IressInput')
199
- return 'components-input-docs.md';
200
- return null;
201
- });
202
- const result = handleAnalyzeComponentMentions(args);
203
- expect(mockUtils.extractIressComponents).toHaveBeenCalledWith('Use IressButton and IressInput components in your form.');
204
- expect(result.content).toHaveLength(1);
205
- expect(result.content[0].type).toBe('text');
206
- expect(result.content[0].text).toContain('Found 2 Iress component mention(s)');
207
- expect(result.content[0].text).toContain('**IressButton** ✅ Available');
208
- expect(result.content[0].text).toContain('**IressInput** ✅ Available');
209
- expect(result.content[0].text).toContain('*Use the `get_iress_component_info` tool with a specific component name for detailed information.*');
210
- });
211
- it('should provide detailed information when detailed flag is true', () => {
212
- const args = {
213
- text: 'Use IressButton component.',
214
- detailed: true,
215
- };
216
- const mockFileContent = `# IressButton
217
- A great button component for interactive elements.
218
- ## Props
219
- - variant: string
220
- `;
221
- mockUtils.extractIressComponents.mockReturnValue(['IressButton']);
222
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
223
- mockUtils.readFileContent.mockReturnValue(mockFileContent);
224
- const result = handleAnalyzeComponentMentions(args);
225
- expect(result.content[0].text).toContain('**IressButton** ✅');
226
- expect(result.content[0].text).toContain('File: components-button-docs.md');
227
- expect(result.content[0].text).toContain('Description: A great button component');
228
- });
229
- it('should handle components that are not found', () => {
230
- const args = {
231
- text: 'Use IressNonExistent component.',
232
- detailed: false,
233
- };
234
- mockUtils.extractIressComponents.mockReturnValue(['IressNonExistent']);
235
- mockUtils.mapIressComponentToFile.mockReturnValue(null);
236
- const result = handleAnalyzeComponentMentions(args);
237
- expect(result.content[0].text).toContain('**IressNonExistent** ❌ Not found');
238
- });
239
- it('should return appropriate message when no components are found', () => {
240
- const args = {
241
- text: 'This text has no component mentions.',
242
- detailed: false,
243
- };
244
- mockUtils.extractIressComponents.mockReturnValue([]);
245
- const result = handleAnalyzeComponentMentions(args);
246
- expect(result.content[0].text).toContain('No Iress component mentions found');
247
- expect(result.content[0].text).toContain("Components should be mentioned with the 'Iress' prefix");
248
- });
249
- it('should handle file reading errors in detailed mode gracefully', () => {
250
- const args = {
251
- text: 'Use IressButton component.',
252
- detailed: true,
253
- };
254
- mockUtils.extractIressComponents.mockReturnValue(['IressButton']);
255
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
256
- mockUtils.readFileContent.mockImplementation(() => {
257
- throw new Error('File read error');
258
- });
259
- const result = handleAnalyzeComponentMentions(args);
260
- expect(result.content[0].text).toContain('**IressButton** ❌');
261
- expect(result.content[0].text).toContain('Error reading documentation');
262
- });
263
- it('should validate input parameters using zod schema', () => {
264
- const invalidArgs = {
265
- text: 123, // Should be string
266
- };
267
- expect(() => handleAnalyzeComponentMentions(invalidArgs)).toThrow();
268
- });
269
- it('should handle missing optional detailed parameter with default false', () => {
270
- const args = {
271
- text: 'Use IressButton component.',
272
- // detailed not provided, should default to false
273
- };
274
- mockUtils.extractIressComponents.mockReturnValue(['IressButton']);
275
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
276
- const result = handleAnalyzeComponentMentions(args);
277
- // Should use summary format (not detailed)
278
- expect(result.content[0].text).toContain('**IressButton** ✅ Available');
279
- expect(result.content[0].text).not.toContain('Description:');
280
- });
281
- it('should not show detailed info prompt when detailed is true', () => {
282
- const args = {
283
- text: 'Use IressButton component.',
284
- detailed: true,
285
- };
286
- const mockFileContent = 'Button documentation';
287
- mockUtils.extractIressComponents.mockReturnValue(['IressButton']);
288
- mockUtils.mapIressComponentToFile.mockReturnValue('components-button-docs.md');
289
- mockUtils.readFileContent.mockReturnValue(mockFileContent);
290
- const result = handleAnalyzeComponentMentions(args);
291
- expect(result.content[0].text).not.toContain('*Use the `get_iress_component_info` tool with a specific component name for detailed information.*');
292
- });
293
- it('should handle mixed found and not found components', () => {
294
- const args = {
295
- text: 'Use IressButton and IressNonExistent components.',
296
- detailed: false,
297
- };
298
- mockUtils.extractIressComponents.mockReturnValue([
299
- 'IressButton',
300
- 'IressNonExistent',
301
- ]);
302
- // Mock the function to return different results for each component
303
- mockUtils.mapIressComponentToFile.mockImplementation((name) => {
304
- if (name === 'IressButton')
305
- return 'components-button-docs.md';
306
- if (name === 'IressNonExistent')
307
- return null;
308
- return null;
309
- });
310
- const result = handleAnalyzeComponentMentions(args);
311
- expect(result.content[0].text).toContain('**IressButton** ✅ Available');
312
- expect(result.content[0].text).toContain('**IressNonExistent** ❌ Not found');
313
- expect(result.content[0].text).toContain('*Use the `get_iress_component_info` tool with a specific component name for detailed information.*');
314
- });
315
- });
316
- });
@@ -1,352 +0,0 @@
1
- /**
2
- * Tests for resourceHandlers.ts
3
- */
4
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
5
- import * as path from 'path';
6
- import { handleListResources, handleReadResource } from './resourceHandlers.js';
7
- import { getMarkdownFiles, readFileContent } from './utils.js';
8
- import { DOCS_DIR } from './config.js';
9
- const mockGetMarkdownFiles = vi.mocked(getMarkdownFiles);
10
- const mockReadFileContent = vi.mocked(readFileContent);
11
- // Mock dependencies
12
- vi.mock('./utils.js', () => ({
13
- getMarkdownFiles: vi.fn(),
14
- readFileContent: vi.fn(),
15
- }));
16
- vi.mock('./config.js', () => ({
17
- DOCS_DIR: '/mock/docs/ids',
18
- }));
19
- describe('resourceHandlers', () => {
20
- beforeEach(() => {
21
- vi.clearAllMocks();
22
- });
23
- afterEach(() => {
24
- vi.restoreAllMocks();
25
- });
26
- describe('handleListResources', () => {
27
- it('should return empty resources when no markdown files exist', () => {
28
- mockGetMarkdownFiles.mockReturnValue([]);
29
- const result = handleListResources();
30
- expect(result).toEqual({
31
- resources: [],
32
- });
33
- expect(mockGetMarkdownFiles).toHaveBeenCalledOnce();
34
- });
35
- it('should categorize component files correctly', () => {
36
- const mockFiles = [
37
- 'components-button-docs.md',
38
- 'components-input-docs.md',
39
- 'components-modal-docs.md',
40
- ];
41
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
42
- const result = handleListResources();
43
- expect(result.resources).toHaveLength(3);
44
- expect(result.resources[0]).toEqual({
45
- uri: `file://${path.join(DOCS_DIR, 'components-button-docs.md')}`,
46
- name: 'Components: button',
47
- description: 'IDS components documentation for button',
48
- mimeType: 'text/markdown',
49
- });
50
- expect(result.resources[1]).toEqual({
51
- uri: `file://${path.join(DOCS_DIR, 'components-input-docs.md')}`,
52
- name: 'Components: input',
53
- description: 'IDS components documentation for input',
54
- mimeType: 'text/markdown',
55
- });
56
- expect(result.resources[2]).toEqual({
57
- uri: `file://${path.join(DOCS_DIR, 'components-modal-docs.md')}`,
58
- name: 'Components: modal',
59
- description: 'IDS components documentation for modal',
60
- mimeType: 'text/markdown',
61
- });
62
- });
63
- it('should categorize foundation files correctly', () => {
64
- const mockFiles = [
65
- 'foundations-colors-docs.md',
66
- 'foundations-typography-docs.md',
67
- 'foundations-spacing-docs.md',
68
- ];
69
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
70
- const result = handleListResources();
71
- expect(result.resources).toHaveLength(3);
72
- expect(result.resources[0]).toEqual({
73
- uri: `file://${path.join(DOCS_DIR, 'foundations-colors-docs.md')}`,
74
- name: 'Foundations: colors',
75
- description: 'IDS foundations documentation for colors',
76
- mimeType: 'text/markdown',
77
- });
78
- expect(result.resources[1]).toEqual({
79
- uri: `file://${path.join(DOCS_DIR, 'foundations-typography-docs.md')}`,
80
- name: 'Foundations: typography',
81
- description: 'IDS foundations documentation for typography',
82
- mimeType: 'text/markdown',
83
- });
84
- expect(result.resources[2]).toEqual({
85
- uri: `file://${path.join(DOCS_DIR, 'foundations-spacing-docs.md')}`,
86
- name: 'Foundations: spacing',
87
- description: 'IDS foundations documentation for spacing',
88
- mimeType: 'text/markdown',
89
- });
90
- });
91
- it('should categorize resource files correctly', () => {
92
- const mockFiles = [
93
- 'resources-guidelines-docs.md',
94
- 'resources-patterns-docs.md',
95
- ];
96
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
97
- const result = handleListResources();
98
- expect(result.resources).toHaveLength(2);
99
- expect(result.resources[0]).toEqual({
100
- uri: `file://${path.join(DOCS_DIR, 'resources-guidelines-docs.md')}`,
101
- name: 'Resources: guidelines',
102
- description: 'IDS resources documentation for guidelines',
103
- mimeType: 'text/markdown',
104
- });
105
- expect(result.resources[1]).toEqual({
106
- uri: `file://${path.join(DOCS_DIR, 'resources-patterns-docs.md')}`,
107
- name: 'Resources: patterns',
108
- description: 'IDS resources documentation for patterns',
109
- mimeType: 'text/markdown',
110
- });
111
- });
112
- it('should categorize introduction files correctly', () => {
113
- const mockFiles = ['introduction.md', 'getting-started-introduction.md'];
114
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
115
- const result = handleListResources();
116
- expect(result.resources).toHaveLength(2);
117
- expect(result.resources[0]).toEqual({
118
- uri: `file://${path.join(DOCS_DIR, 'introduction.md')}`,
119
- name: 'Getting Started: Introduction',
120
- description: 'IDS getting started documentation for Introduction',
121
- mimeType: 'text/markdown',
122
- });
123
- expect(result.resources[1]).toEqual({
124
- uri: `file://${path.join(DOCS_DIR, 'getting-started-introduction.md')}`,
125
- name: 'Getting Started: Introduction',
126
- description: 'IDS getting started documentation for Introduction',
127
- mimeType: 'text/markdown',
128
- });
129
- });
130
- it('should categorize uncategorized files as Other', () => {
131
- const mockFiles = ['readme.md', 'changelog.md', 'random-file.md'];
132
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
133
- const result = handleListResources();
134
- expect(result.resources).toHaveLength(3);
135
- expect(result.resources[0]).toEqual({
136
- uri: `file://${path.join(DOCS_DIR, 'readme.md')}`,
137
- name: 'Other: readme.md',
138
- description: 'IDS other documentation for readme.md',
139
- mimeType: 'text/markdown',
140
- });
141
- expect(result.resources[1]).toEqual({
142
- uri: `file://${path.join(DOCS_DIR, 'changelog.md')}`,
143
- name: 'Other: changelog.md',
144
- description: 'IDS other documentation for changelog.md',
145
- mimeType: 'text/markdown',
146
- });
147
- expect(result.resources[2]).toEqual({
148
- uri: `file://${path.join(DOCS_DIR, 'random-file.md')}`,
149
- name: 'Other: random-file.md',
150
- description: 'IDS other documentation for random-file.md',
151
- mimeType: 'text/markdown',
152
- });
153
- });
154
- it('should handle mixed file categories', () => {
155
- const mockFiles = [
156
- 'components-button-docs.md',
157
- 'foundations-colors-docs.md',
158
- 'resources-guidelines-docs.md',
159
- 'introduction.md',
160
- 'readme.md',
161
- ];
162
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
163
- const result = handleListResources();
164
- expect(result.resources).toHaveLength(5);
165
- // Check that each category is represented
166
- const categories = result.resources.map((resource) => resource.name.split(':')[0]);
167
- expect(categories).toContain('Components');
168
- expect(categories).toContain('Foundations');
169
- expect(categories).toContain('Resources');
170
- expect(categories).toContain('Getting Started');
171
- expect(categories).toContain('Other');
172
- });
173
- });
174
- describe('handleReadResource', () => {
175
- const mockFileContent = '# Test Documentation\n\nThis is test content.';
176
- beforeEach(() => {
177
- mockReadFileContent.mockReturnValue(mockFileContent);
178
- });
179
- it('should successfully read a valid file', () => {
180
- const testFilePath = path.join(DOCS_DIR, 'components-button-docs.md');
181
- const request = {
182
- params: {
183
- uri: `file://${testFilePath}`,
184
- },
185
- };
186
- const result = handleReadResource(request);
187
- expect(result).toEqual({
188
- contents: [
189
- {
190
- uri: request.params.uri,
191
- mimeType: 'text/markdown',
192
- text: mockFileContent,
193
- },
194
- ],
195
- });
196
- expect(mockReadFileContent).toHaveBeenCalledWith(testFilePath);
197
- });
198
- it('should throw error for unsupported protocol', () => {
199
- const request = {
200
- params: {
201
- uri: 'http://example.com/file.md',
202
- },
203
- };
204
- expect(() => handleReadResource(request)).toThrow('Unsupported protocol: http:');
205
- });
206
- it('should throw error for files outside docs directory', () => {
207
- const request = {
208
- params: {
209
- uri: 'file:///etc/passwd',
210
- },
211
- };
212
- expect(() => handleReadResource(request)).toThrow('Access denied: File is outside the docs directory');
213
- });
214
- it('should throw error for files using relative path traversal', () => {
215
- const testFilePath = path.join(DOCS_DIR, '..', '..', 'secret.md');
216
- const request = {
217
- params: {
218
- uri: `file://${testFilePath}`,
219
- },
220
- };
221
- expect(() => handleReadResource(request)).toThrow('Access denied: File is outside the docs directory');
222
- });
223
- it('should handle file read errors gracefully', () => {
224
- const testFilePath = path.join(DOCS_DIR, 'nonexistent-file.md');
225
- const readError = new Error('File not found');
226
- mockReadFileContent.mockImplementation(() => {
227
- throw readError;
228
- });
229
- const request = {
230
- params: {
231
- uri: `file://${testFilePath}`,
232
- },
233
- };
234
- expect(() => handleReadResource(request)).toThrow('Failed to read file: File not found');
235
- expect(mockReadFileContent).toHaveBeenCalledWith(testFilePath);
236
- });
237
- it('should handle unknown errors gracefully', () => {
238
- const testFilePath = path.join(DOCS_DIR, 'components-button-docs.md');
239
- mockReadFileContent.mockImplementation(() => {
240
- throw new Error('Unknown error string'); // Proper Error object
241
- });
242
- const request = {
243
- params: {
244
- uri: `file://${testFilePath}`,
245
- },
246
- };
247
- expect(() => handleReadResource(request)).toThrow('Failed to read file: Unknown error string');
248
- });
249
- it('should handle files in subdirectories within docs directory', () => {
250
- const testFilePath = path.join(DOCS_DIR, 'subdirectory', 'components-nested-docs.md');
251
- const request = {
252
- params: {
253
- uri: `file://${testFilePath}`,
254
- },
255
- };
256
- const result = handleReadResource(request);
257
- expect(result).toEqual({
258
- contents: [
259
- {
260
- uri: request.params.uri,
261
- mimeType: 'text/markdown',
262
- text: mockFileContent,
263
- },
264
- ],
265
- });
266
- expect(mockReadFileContent).toHaveBeenCalledWith(testFilePath);
267
- });
268
- it('should handle Windows-style paths correctly', () => {
269
- const normalizedPath = path.join(DOCS_DIR, 'components-button-docs.md');
270
- const windowsStyleUri = `file://${normalizedPath.replace(/\\/g, '/')}`;
271
- const request = {
272
- params: {
273
- uri: windowsStyleUri,
274
- },
275
- };
276
- const result = handleReadResource(request);
277
- expect(result).toEqual({
278
- contents: [
279
- {
280
- uri: request.params.uri,
281
- mimeType: 'text/markdown',
282
- text: mockFileContent,
283
- },
284
- ],
285
- });
286
- });
287
- });
288
- // Add type-specific tests at the end
289
- describe('Type compliance tests', () => {
290
- it('should return resources with correct interface structure', () => {
291
- const mockFiles = ['components-button-docs.md'];
292
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
293
- const result = handleListResources();
294
- // Verify the returned structure matches expected interface
295
- expect(result).toHaveProperty('resources');
296
- expect(Array.isArray(result.resources)).toBe(true);
297
- if (result.resources.length > 0) {
298
- const resource = result.resources[0];
299
- expect(resource).toHaveProperty('uri');
300
- expect(resource).toHaveProperty('name');
301
- expect(resource).toHaveProperty('description');
302
- expect(resource).toHaveProperty('mimeType');
303
- expect(typeof resource.uri).toBe('string');
304
- expect(typeof resource.name).toBe('string');
305
- expect(typeof resource.description).toBe('string');
306
- expect(resource.mimeType).toBe('text/markdown');
307
- }
308
- });
309
- it('should return read resource with correct content structure', () => {
310
- const mockContent = '# Component Documentation\n\nDescription here.';
311
- mockReadFileContent.mockReturnValue(mockContent);
312
- const testFilePath = path.join(DOCS_DIR, 'components-button-docs.md');
313
- const request = {
314
- params: {
315
- uri: `file://${testFilePath}`,
316
- },
317
- };
318
- const result = handleReadResource(request);
319
- // Verify the returned structure matches expected interface
320
- expect(result).toHaveProperty('contents');
321
- expect(Array.isArray(result.contents)).toBe(true);
322
- expect(result.contents).toHaveLength(1);
323
- const content = result.contents[0];
324
- expect(content).toHaveProperty('uri');
325
- expect(content).toHaveProperty('mimeType');
326
- expect(content).toHaveProperty('text');
327
- expect(typeof content.uri).toBe('string');
328
- expect(content.mimeType).toBe('text/markdown');
329
- expect(typeof content.text).toBe('string');
330
- expect(content.text).toBe(mockContent);
331
- });
332
- it('should handle empty file names correctly', () => {
333
- const mockFiles = ['', ' ', '.md'];
334
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
335
- const result = handleListResources();
336
- expect(result.resources).toHaveLength(3);
337
- // All should be categorized as "Other" since they don't match patterns
338
- result.resources.forEach((resource) => {
339
- expect(resource.name).toMatch(/^Other:/);
340
- });
341
- });
342
- it('should preserve original filename in Other category', () => {
343
- const originalFilename = 'some-random-documentation.md';
344
- const mockFiles = [originalFilename];
345
- mockGetMarkdownFiles.mockReturnValue(mockFiles);
346
- const result = handleListResources();
347
- expect(result.resources).toHaveLength(1);
348
- expect(result.resources[0].name).toBe(`Other: ${originalFilename}`);
349
- expect(result.resources[0].description).toBe(`IDS other documentation for ${originalFilename}`);
350
- });
351
- });
352
- });