diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8d818..10fc365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ ## Unreleased +### Features Added + +- **Granular Documentation Resources**: Split monolithic documentation resource into focused, category-specific resources (#69) + - New `resource://mapbox-api-reference` - REST API documentation only + - New `resource://mapbox-sdk-docs` - SDK and client library documentation + - New `resource://mapbox-guides` - Tutorials, how-tos, and guides + - New `resource://mapbox-examples` - Code examples and API playgrounds + - New `resource://mapbox-reference` - Reference materials (tilesets, data products, etc.) + - Improved performance by allowing clients to fetch only relevant documentation + - Better organization for AI assistants to find the right information + - Original `resource://mapbox-documentation` kept for backward compatibility with deprecation notice + - Added shared `docParser` utility for parsing and categorizing documentation sections + ### Documentation - **PR Guidelines**: Added CHANGELOG requirement to CLAUDE.md (#67) diff --git a/src/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.ts b/src/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.ts new file mode 100644 index 0000000..1d1d9f3 --- /dev/null +++ b/src/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.ts @@ -0,0 +1,77 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ReadResourceResult, + ServerNotification, + ServerRequest +} from '@modelcontextprotocol/sdk/types.js'; +import type { HttpRequest } from '../../utils/types.js'; +import { BaseResource } from '../BaseResource.js'; +import { + parseDocSections, + filterSectionsByCategory, + sectionsToMarkdown +} from '../utils/docParser.js'; + +/** + * Resource providing Mapbox API reference documentation + */ +export class MapboxApiReferenceResource extends BaseResource { + readonly name = 'Mapbox API Reference'; + readonly uri = 'resource://mapbox-api-reference'; + readonly description = + 'Mapbox REST API reference documentation including endpoints, parameters, rate limits, and authentication for all Mapbox APIs (Geocoding, Directions, Static Images, Tilequery, etc.)'; + readonly mimeType = 'text/markdown'; + + private httpRequest: HttpRequest; + + constructor(params: { httpRequest: HttpRequest }) { + super(); + this.httpRequest = params.httpRequest; + } + + public async readCallback( + uri: URL, + _extra: RequestHandlerExtra + ): Promise { + try { + const response = await this.httpRequest( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Mapbox documentation: ${response.statusText}` + ); + } + + const content = await response.text(); + + // Parse and filter for API sections only + const allSections = parseDocSections(content); + const apiSections = filterSectionsByCategory(allSections, 'apis'); + const apiContent = sectionsToMarkdown(apiSections); + + return { + contents: [ + { + uri: uri.href, + mimeType: this.mimeType, + text: `# Mapbox API Reference\n\n${apiContent}` + } + ] + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + throw new Error(`Failed to fetch Mapbox API reference: ${errorMessage}`); + } + } +} diff --git a/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts b/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts index 9fea819..c894dbb 100644 --- a/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts +++ b/src/resources/mapbox-documentation-resource/MapboxDocumentationResource.ts @@ -13,12 +13,21 @@ import { BaseResource } from '../BaseResource.js'; /** * Resource providing the latest official Mapbox documentation * fetched from docs.mapbox.com/llms.txt + * + * @deprecated Use the granular resources instead for better performance and organization: + * - resource://mapbox-api-reference (REST API docs) + * - resource://mapbox-sdk-docs (SDK documentation) + * - resource://mapbox-guides (Tutorials and how-tos) + * - resource://mapbox-examples (Code examples and playgrounds) + * - resource://mapbox-reference (Tilesets, data products, etc.) + * + * This resource is kept for backward compatibility. */ export class MapboxDocumentationResource extends BaseResource { readonly name = 'Mapbox Documentation'; readonly uri = 'resource://mapbox-documentation'; readonly description = - 'Latest official Mapbox documentation, APIs, SDKs, and developer resources. Always up-to-date comprehensive coverage of all current Mapbox services.'; + '[DEPRECATED: Use granular resources like resource://mapbox-api-reference instead] Latest official Mapbox documentation, APIs, SDKs, and developer resources. Always up-to-date comprehensive coverage of all current Mapbox services.'; readonly mimeType = 'text/markdown'; private httpRequest: HttpRequest; diff --git a/src/resources/mapbox-examples-resource/MapboxExamplesResource.ts b/src/resources/mapbox-examples-resource/MapboxExamplesResource.ts new file mode 100644 index 0000000..0c811f6 --- /dev/null +++ b/src/resources/mapbox-examples-resource/MapboxExamplesResource.ts @@ -0,0 +1,77 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ReadResourceResult, + ServerNotification, + ServerRequest +} from '@modelcontextprotocol/sdk/types.js'; +import type { HttpRequest } from '../../utils/types.js'; +import { BaseResource } from '../BaseResource.js'; +import { + parseDocSections, + filterSectionsByCategory, + sectionsToMarkdown +} from '../utils/docParser.js'; + +/** + * Resource providing Mapbox examples and playgrounds + */ +export class MapboxExamplesResource extends BaseResource { + readonly name = 'Mapbox Examples'; + readonly uri = 'resource://mapbox-examples'; + readonly description = + 'Mapbox code examples, API playgrounds, and interactive demos for testing and learning'; + readonly mimeType = 'text/markdown'; + + private httpRequest: HttpRequest; + + constructor(params: { httpRequest: HttpRequest }) { + super(); + this.httpRequest = params.httpRequest; + } + + public async readCallback( + uri: URL, + _extra: RequestHandlerExtra + ): Promise { + try { + const response = await this.httpRequest( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Mapbox documentation: ${response.statusText}` + ); + } + + const content = await response.text(); + + // Parse and filter for example sections only + const allSections = parseDocSections(content); + const exampleSections = filterSectionsByCategory(allSections, 'examples'); + const exampleContent = sectionsToMarkdown(exampleSections); + + return { + contents: [ + { + uri: uri.href, + mimeType: this.mimeType, + text: `# Mapbox Examples\n\n${exampleContent}` + } + ] + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + throw new Error(`Failed to fetch Mapbox examples: ${errorMessage}`); + } + } +} diff --git a/src/resources/mapbox-guides-resource/MapboxGuidesResource.ts b/src/resources/mapbox-guides-resource/MapboxGuidesResource.ts new file mode 100644 index 0000000..db9094e --- /dev/null +++ b/src/resources/mapbox-guides-resource/MapboxGuidesResource.ts @@ -0,0 +1,77 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ReadResourceResult, + ServerNotification, + ServerRequest +} from '@modelcontextprotocol/sdk/types.js'; +import type { HttpRequest } from '../../utils/types.js'; +import { BaseResource } from '../BaseResource.js'; +import { + parseDocSections, + filterSectionsByCategory, + sectionsToMarkdown +} from '../utils/docParser.js'; + +/** + * Resource providing Mapbox guides and tutorials + */ +export class MapboxGuidesResource extends BaseResource { + readonly name = 'Mapbox Guides'; + readonly uri = 'resource://mapbox-guides'; + readonly description = + 'Mapbox guides, tutorials, and how-tos including Studio Manual, map design guides, and best practices'; + readonly mimeType = 'text/markdown'; + + private httpRequest: HttpRequest; + + constructor(params: { httpRequest: HttpRequest }) { + super(); + this.httpRequest = params.httpRequest; + } + + public async readCallback( + uri: URL, + _extra: RequestHandlerExtra + ): Promise { + try { + const response = await this.httpRequest( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Mapbox documentation: ${response.statusText}` + ); + } + + const content = await response.text(); + + // Parse and filter for guide sections only + const allSections = parseDocSections(content); + const guideSections = filterSectionsByCategory(allSections, 'guides'); + const guideContent = sectionsToMarkdown(guideSections); + + return { + contents: [ + { + uri: uri.href, + mimeType: this.mimeType, + text: `# Mapbox Guides\n\n${guideContent}` + } + ] + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + throw new Error(`Failed to fetch Mapbox guides: ${errorMessage}`); + } + } +} diff --git a/src/resources/mapbox-reference-resource/MapboxReferenceResource.ts b/src/resources/mapbox-reference-resource/MapboxReferenceResource.ts new file mode 100644 index 0000000..19a88e1 --- /dev/null +++ b/src/resources/mapbox-reference-resource/MapboxReferenceResource.ts @@ -0,0 +1,80 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ReadResourceResult, + ServerNotification, + ServerRequest +} from '@modelcontextprotocol/sdk/types.js'; +import type { HttpRequest } from '../../utils/types.js'; +import { BaseResource } from '../BaseResource.js'; +import { + parseDocSections, + filterSectionsByCategory, + sectionsToMarkdown +} from '../utils/docParser.js'; + +/** + * Resource providing Mapbox reference documentation + */ +export class MapboxReferenceResource extends BaseResource { + readonly name = 'Mapbox Reference'; + readonly uri = 'resource://mapbox-reference'; + readonly description = + 'Mapbox reference documentation including tilesets, data products, accounts, pricing, and other reference materials'; + readonly mimeType = 'text/markdown'; + + private httpRequest: HttpRequest; + + constructor(params: { httpRequest: HttpRequest }) { + super(); + this.httpRequest = params.httpRequest; + } + + public async readCallback( + uri: URL, + _extra: RequestHandlerExtra + ): Promise { + try { + const response = await this.httpRequest( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Mapbox documentation: ${response.statusText}` + ); + } + + const content = await response.text(); + + // Parse and filter for reference sections only + const allSections = parseDocSections(content); + const referenceSections = filterSectionsByCategory( + allSections, + 'reference' + ); + const referenceContent = sectionsToMarkdown(referenceSections); + + return { + contents: [ + { + uri: uri.href, + mimeType: this.mimeType, + text: `# Mapbox Reference\n\n${referenceContent}` + } + ] + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + throw new Error(`Failed to fetch Mapbox reference: ${errorMessage}`); + } + } +} diff --git a/src/resources/mapbox-sdk-docs-resource/MapboxSdkDocsResource.ts b/src/resources/mapbox-sdk-docs-resource/MapboxSdkDocsResource.ts new file mode 100644 index 0000000..2efa672 --- /dev/null +++ b/src/resources/mapbox-sdk-docs-resource/MapboxSdkDocsResource.ts @@ -0,0 +1,77 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js'; +import type { + ReadResourceResult, + ServerNotification, + ServerRequest +} from '@modelcontextprotocol/sdk/types.js'; +import type { HttpRequest } from '../../utils/types.js'; +import { BaseResource } from '../BaseResource.js'; +import { + parseDocSections, + filterSectionsByCategory, + sectionsToMarkdown +} from '../utils/docParser.js'; + +/** + * Resource providing Mapbox SDK documentation + */ +export class MapboxSdkDocsResource extends BaseResource { + readonly name = 'Mapbox SDK Documentation'; + readonly uri = 'resource://mapbox-sdk-docs'; + readonly description = + 'Mapbox SDK and client library documentation for mobile (iOS, Android, Flutter) and web (Mapbox GL JS, Search JS) platforms'; + readonly mimeType = 'text/markdown'; + + private httpRequest: HttpRequest; + + constructor(params: { httpRequest: HttpRequest }) { + super(); + this.httpRequest = params.httpRequest; + } + + public async readCallback( + uri: URL, + _extra: RequestHandlerExtra + ): Promise { + try { + const response = await this.httpRequest( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + if (!response.ok) { + throw new Error( + `Failed to fetch Mapbox documentation: ${response.statusText}` + ); + } + + const content = await response.text(); + + // Parse and filter for SDK sections only + const allSections = parseDocSections(content); + const sdkSections = filterSectionsByCategory(allSections, 'sdks'); + const sdkContent = sectionsToMarkdown(sdkSections); + + return { + contents: [ + { + uri: uri.href, + mimeType: this.mimeType, + text: `# Mapbox SDK Documentation\n\n${sdkContent}` + } + ] + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + throw new Error(`Failed to fetch Mapbox SDK docs: ${errorMessage}`); + } + } +} diff --git a/src/resources/resourceRegistry.ts b/src/resources/resourceRegistry.ts index 5288bd8..9c55a94 100644 --- a/src/resources/resourceRegistry.ts +++ b/src/resources/resourceRegistry.ts @@ -6,6 +6,11 @@ import { MapboxStreetsV8FieldsResource } from './mapbox-streets-v8-fields-resour import { MapboxTokenScopesResource } from './mapbox-token-scopes-resource/MapboxTokenScopesResource.js'; import { MapboxLayerTypeMappingResource } from './mapbox-layer-type-mapping-resource/MapboxLayerTypeMappingResource.js'; import { MapboxDocumentationResource } from './mapbox-documentation-resource/MapboxDocumentationResource.js'; +import { MapboxApiReferenceResource } from './mapbox-api-reference-resource/MapboxApiReferenceResource.js'; +import { MapboxSdkDocsResource } from './mapbox-sdk-docs-resource/MapboxSdkDocsResource.js'; +import { MapboxGuidesResource } from './mapbox-guides-resource/MapboxGuidesResource.js'; +import { MapboxExamplesResource } from './mapbox-examples-resource/MapboxExamplesResource.js'; +import { MapboxReferenceResource } from './mapbox-reference-resource/MapboxReferenceResource.js'; import { httpRequest } from '../utils/httpPipeline.js'; // Central registry of all resources @@ -14,7 +19,13 @@ export const ALL_RESOURCES = [ new MapboxStreetsV8FieldsResource(), new MapboxTokenScopesResource(), new MapboxLayerTypeMappingResource(), - new MapboxDocumentationResource({ httpRequest }) + new MapboxDocumentationResource({ httpRequest }), // Kept for backward compatibility + // New granular documentation resources + new MapboxApiReferenceResource({ httpRequest }), + new MapboxSdkDocsResource({ httpRequest }), + new MapboxGuidesResource({ httpRequest }), + new MapboxExamplesResource({ httpRequest }), + new MapboxReferenceResource({ httpRequest }) ] as const; export type ResourceInstance = (typeof ALL_RESOURCES)[number]; diff --git a/src/resources/utils/docParser.ts b/src/resources/utils/docParser.ts new file mode 100644 index 0000000..1750c71 --- /dev/null +++ b/src/resources/utils/docParser.ts @@ -0,0 +1,126 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +/** + * Parsed documentation section + */ +export interface DocSection { + title: string; + content: string; +} + +/** + * Category type for documentation sections + */ +export type DocCategory = 'apis' | 'sdks' | 'guides' | 'examples' | 'reference'; + +/** + * Parse Mapbox documentation into sections + */ +export function parseDocSections(content: string): DocSection[] { + const sections: DocSection[] = []; + const lines = content.split('\n'); + + let currentSection: DocSection | null = null; + let currentContent: string[] = []; + + for (const line of lines) { + // Detect section headers (lines starting with ##) + if (line.startsWith('##')) { + // Save previous section if exists + if (currentSection && currentContent.length > 0) { + currentSection.content = currentContent.join('\n').trim(); + sections.push(currentSection); + } + + // Start new section + const title = line.replace(/^##\s*/, '').trim(); + currentSection = { + title, + content: '' + }; + currentContent = []; + } else if (currentSection) { + // Add content to current section + currentContent.push(line); + } + } + + // Save last section + if (currentSection && currentContent.length > 0) { + currentSection.content = currentContent.join('\n').trim(); + sections.push(currentSection); + } + + return sections; +} + +/** + * Categorize a section based on its title + */ +export function categorizeSection(title: string): DocCategory { + const lower = title.toLowerCase(); + + // APIs category + if ( + lower.includes('api') && + !lower.includes('playground') && + !lower.includes('sdk') && + !lower.includes('libraries') + ) { + return 'apis'; + } + + // SDKs category + if ( + lower.includes('sdk') || + lower.includes('library') || + lower.includes('libraries') || + lower.includes('client') + ) { + return 'sdks'; + } + + // Examples category + if ( + lower.includes('playground') || + lower.includes('demo') || + lower.includes('example') + ) { + return 'examples'; + } + + // Guides category + if ( + lower.includes('design') || + lower.includes('studio') || + lower.includes('manual') || + lower.includes('guide') + ) { + return 'guides'; + } + + // Everything else is reference + return 'reference'; +} + +/** + * Filter sections by category + */ +export function filterSectionsByCategory( + sections: DocSection[], + category: DocCategory +): DocSection[] { + return sections.filter( + (section) => categorizeSection(section.title) === category + ); +} + +/** + * Convert sections back to markdown + */ +export function sectionsToMarkdown(sections: DocSection[]): string { + return sections + .map((section) => `## ${section.title}\n\n${section.content}`) + .join('\n\n'); +} diff --git a/test/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.test.ts b/test/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.test.ts new file mode 100644 index 0000000..0af7c48 --- /dev/null +++ b/test/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.test.ts @@ -0,0 +1,144 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { describe, it, expect, vi } from 'vitest'; +import { MapboxApiReferenceResource } from '../../../src/resources/mapbox-api-reference-resource/MapboxApiReferenceResource.js'; + +describe('MapboxApiReferenceResource', () => { + const MOCK_DOCS = `# Mapbox Documentation + +> Introduction text + +## Maps client libraries & SDKs + +- [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/) +- [iOS SDK](https://docs.mapbox.com/ios/) + +## Geocoding API + +- [API Docs](https://docs.mapbox.com/api/search/geocoding/) +- [API Playground](https://docs.mapbox.com/playground/geocoding/) + +## Map Design + +- [Studio Manual](https://docs.mapbox.com/studio-manual/) + +## API Playgrounds + +- [Static Images Playground](https://docs.mapbox.com/playground/static/) + +## Mapbox Tilesets + +- [Tileset Reference](https://docs.mapbox.com/data/tilesets/reference/) +`; + + it('should have correct metadata', () => { + const mockHttpRequest = vi.fn(); + const resource = new MapboxApiReferenceResource({ + httpRequest: mockHttpRequest + }); + + expect(resource.name).toBe('Mapbox API Reference'); + expect(resource.uri).toBe('resource://mapbox-api-reference'); + expect(resource.description).toContain('REST API reference'); + expect(resource.mimeType).toBe('text/markdown'); + }); + + it('should fetch and filter API documentation', async () => { + const mockHttpRequest = vi.fn().mockResolvedValue({ + ok: true, + text: async () => MOCK_DOCS + }); + + const resource = new MapboxApiReferenceResource({ + httpRequest: mockHttpRequest + }); + + const result = await resource.readCallback( + new URL('resource://mapbox-api-reference'), + {} as any + ); + + expect(mockHttpRequest).toHaveBeenCalledWith( + 'https://docs.mapbox.com/llms.txt', + { + headers: { + Accept: 'text/markdown, text/plain;q=0.9, */*;q=0.8' + } + } + ); + + expect(result.contents).toHaveLength(1); + expect(result.contents[0].uri).toBe('resource://mapbox-api-reference'); + expect(result.contents[0].mimeType).toBe('text/markdown'); + expect(result.contents[0].text).toContain('# Mapbox API Reference'); + expect(result.contents[0].text).toContain('## Geocoding API'); + expect(result.contents[0].text).not.toContain( + '## Maps client libraries & SDKs' + ); + expect(result.contents[0].text).not.toContain('## Map Design'); + expect(result.contents[0].text).not.toContain('## API Playgrounds'); + }); + + it('should handle HTTP errors', async () => { + const mockHttpRequest = vi.fn().mockResolvedValue({ + ok: false, + statusText: 'Not Found' + }); + + const resource = new MapboxApiReferenceResource({ + httpRequest: mockHttpRequest + }); + + await expect( + resource.readCallback( + new URL('resource://mapbox-api-reference'), + {} as any + ) + ).rejects.toThrow( + 'Failed to fetch Mapbox API reference: Failed to fetch Mapbox documentation: Not Found' + ); + }); + + it('should handle network errors', async () => { + const mockHttpRequest = vi + .fn() + .mockRejectedValue(new Error('Network error')); + + const resource = new MapboxApiReferenceResource({ + httpRequest: mockHttpRequest + }); + + await expect( + resource.readCallback( + new URL('resource://mapbox-api-reference'), + {} as any + ) + ).rejects.toThrow('Failed to fetch Mapbox API reference: Network error'); + }); + + it('should only include API sections, not playgrounds', async () => { + const mockHttpRequest = vi.fn().mockResolvedValue({ + ok: true, + text: async () => MOCK_DOCS + }); + + const resource = new MapboxApiReferenceResource({ + httpRequest: mockHttpRequest + }); + + const result = await resource.readCallback( + new URL('resource://mapbox-api-reference'), + {} as any + ); + + const content = result.contents[0].text; + + // Should include API sections + expect(content).toContain('## Geocoding API'); + + // Should NOT include playgrounds (those go in examples) + expect(content).not.toContain('## API Playgrounds'); + expect(content).not.toContain('Static Images Playground'); + }); +}); diff --git a/test/resources/utils/docParser.test.ts b/test/resources/utils/docParser.test.ts new file mode 100644 index 0000000..ffbd0ab --- /dev/null +++ b/test/resources/utils/docParser.test.ts @@ -0,0 +1,144 @@ +// Copyright (c) Mapbox, Inc. +// Licensed under the MIT License. + +import { describe, it, expect } from 'vitest'; +import { + parseDocSections, + categorizeSection, + filterSectionsByCategory, + sectionsToMarkdown +} from '../../../src/resources/utils/docParser.js'; + +describe('docParser', () => { + const MOCK_DOCS = `# Mapbox Documentation + +> Introduction text + +## Maps client libraries & SDKs + +- [Mapbox GL JS](https://docs.mapbox.com/mapbox-gl-js/) +- [iOS SDK](https://docs.mapbox.com/ios/) + +## Geocoding API + +- [API Docs](https://docs.mapbox.com/api/search/geocoding/) +- [API Playground](https://docs.mapbox.com/playground/geocoding/) + +## Map Design + +- [Studio Manual](https://docs.mapbox.com/studio-manual/) + +## API Playgrounds + +- [Static Images Playground](https://docs.mapbox.com/playground/static/) + +## Mapbox Tilesets + +- [Tileset Reference](https://docs.mapbox.com/data/tilesets/reference/) +`; + + describe('parseDocSections', () => { + it('should parse documentation into sections', () => { + const sections = parseDocSections(MOCK_DOCS); + + expect(sections).toHaveLength(5); + expect(sections[0].title).toBe('Maps client libraries & SDKs'); + expect(sections[0].content).toContain('Mapbox GL JS'); + expect(sections[1].title).toBe('Geocoding API'); + expect(sections[2].title).toBe('Map Design'); + }); + + it('should handle empty content', () => { + const sections = parseDocSections(''); + expect(sections).toEqual([]); + }); + + it('should handle content without sections', () => { + const sections = parseDocSections('Some text without sections'); + expect(sections).toEqual([]); + }); + }); + + describe('categorizeSection', () => { + it('should categorize API sections', () => { + expect(categorizeSection('Geocoding API')).toBe('apis'); + expect(categorizeSection('Directions API')).toBe('apis'); + expect(categorizeSection('Tiling & rendering APIs')).toBe('apis'); + }); + + it('should categorize SDK sections', () => { + expect(categorizeSection('Maps client libraries & SDKs')).toBe('sdks'); + expect(categorizeSection('Navigation SDK')).toBe('sdks'); + expect(categorizeSection('Android Core library')).toBe('sdks'); + }); + + it('should categorize guide sections', () => { + expect(categorizeSection('Map Design')).toBe('guides'); + expect(categorizeSection('Studio Manual')).toBe('guides'); + }); + + it('should categorize example sections', () => { + expect(categorizeSection('API Playgrounds')).toBe('examples'); + expect(categorizeSection('Demo Projects')).toBe('examples'); + }); + + it('should categorize reference sections by default', () => { + expect(categorizeSection('Mapbox Tilesets')).toBe('reference'); + expect(categorizeSection('Accounts and Pricing')).toBe('reference'); + expect(categorizeSection('Random Section')).toBe('reference'); + }); + }); + + describe('filterSectionsByCategory', () => { + it('should filter sections by category', () => { + const sections = parseDocSections(MOCK_DOCS); + + const apiSections = filterSectionsByCategory(sections, 'apis'); + expect(apiSections).toHaveLength(1); + expect(apiSections[0].title).toBe('Geocoding API'); + + const sdkSections = filterSectionsByCategory(sections, 'sdks'); + expect(sdkSections).toHaveLength(1); + expect(sdkSections[0].title).toBe('Maps client libraries & SDKs'); + + const guideSections = filterSectionsByCategory(sections, 'guides'); + expect(guideSections).toHaveLength(1); + expect(guideSections[0].title).toBe('Map Design'); + + const exampleSections = filterSectionsByCategory(sections, 'examples'); + expect(exampleSections).toHaveLength(1); + expect(exampleSections[0].title).toBe('API Playgrounds'); + + const referenceSections = filterSectionsByCategory(sections, 'reference'); + expect(referenceSections).toHaveLength(1); + expect(referenceSections[0].title).toBe('Mapbox Tilesets'); + }); + + it('should return empty array when no sections match', () => { + const sections = parseDocSections('## Some Random Section\n\nContent'); + const apiSections = filterSectionsByCategory(sections, 'apis'); + expect(apiSections).toEqual([]); + }); + }); + + describe('sectionsToMarkdown', () => { + it('should convert sections back to markdown', () => { + const sections = [ + { title: 'Section 1', content: 'Content 1' }, + { title: 'Section 2', content: 'Content 2' } + ]; + + const markdown = sectionsToMarkdown(sections); + + expect(markdown).toContain('## Section 1'); + expect(markdown).toContain('Content 1'); + expect(markdown).toContain('## Section 2'); + expect(markdown).toContain('Content 2'); + }); + + it('should handle empty sections array', () => { + const markdown = sectionsToMarkdown([]); + expect(markdown).toBe(''); + }); + }); +});