diff --git a/src/extensions/index.ts b/src/extensions/index.ts index 5a1b1e0..8c8bcd5 100644 --- a/src/extensions/index.ts +++ b/src/extensions/index.ts @@ -9,6 +9,7 @@ import { RootKeys, InfoKeys, + ComponentsKeys, OperationKeys, ParameterKeys, SchemaKeys, @@ -18,13 +19,21 @@ import { TagKeys, ExternalDocsKeys, WebhookKeys, - OAuthFlowKeys, + PathItemKeys, + RequestBodyKeys, + MediaTypeKeys, + EncodingKeys, + HeaderKeys, + LinkKeys, + ExampleKeys, + DiscriminatorKeys, + XMLKeys, ContactKeys, LicenseKeys, - ComponentsKeys, + OAuthFlowKeys, ServerVariableKeys, } from '../keys.js'; -import { getVendorExtensions, type VendorModule } from './vendor-loader.js'; +import { type VendorModule } from './vendor-loader.js'; /** * Type-safe context-specific extension functions @@ -528,6 +537,307 @@ export interface VendorExtensions { * ``` */ 'securityDefinitions'?: ContextExtensionFunction<'securityDefinitions'>; + /** Components section extensions + * + * Available keys: + * - `securitySchemes` + * - `pathItems` + * - `parameters` + * - `headers` + * - `requestBodies` + * - `responses` + * - `callbacks` + * - `links` + * - `schemas` + * - `examples` + * + * @example + * ```typescript + * 'components': (before, after) => { + * return { + * 'x-my-extension': before('schemas'), + * 'x-my-config': after('examples') + * }; + * } + * ``` + */ + 'components'?: ContextExtensionFunction<'components'>; + /** Path item extensions + * + * Available keys: + * - `$ref` + * - `summary` + * - `description` + * - `servers` + * - `parameters` + * - `get` + * - `put` + * - `post` + * - `patch` + * - `delete` + * - `options` + * - `head` + * - `trace` + * + * @example + * ```typescript + * 'pathItem': (before, after) => { + * return { + * 'x-my-extension': before('summary'), + * 'x-my-config': after('parameters') + * }; + * } + * ``` + */ + 'pathItem'?: ContextExtensionFunction<'pathItem'>; + /** Request body extensions + * + * Available keys: + * - `description` + * - `required` + * - `content` + * + * @example + * ```typescript + * 'requestBody': (before, after) => { + * return { + * 'x-my-extension': before('description'), + * 'x-my-config': after('content') + * }; + * } + * ``` + */ + 'requestBody'?: ContextExtensionFunction<'requestBody'>; + /** Media type extensions + * + * Available keys: + * - `schema` + * - `example` + * - `examples` + * - `encoding` + * + * @example + * ```typescript + * 'mediaType': (before, after) => { + * return { + * 'x-my-extension': before('schema'), + * 'x-my-config': after('example') + * }; + * } + * ``` + */ + 'mediaType'?: ContextExtensionFunction<'mediaType'>; + /** Encoding extensions + * + * Available keys: + * - `contentType` + * - `style` + * - `explode` + * - `allowReserved` + * - `headers` + * + * @example + * ```typescript + * 'encoding': (before, after) => { + * return { + * 'x-my-extension': before('contentType'), + * 'x-my-config': after('headers') + * }; + * } + * ``` + */ + 'encoding'?: ContextExtensionFunction<'encoding'>; + /** Header extensions + * + * Available keys: + * - `description` + * - `required` + * - `deprecated` + * - `schema` + * - `content` + * - `type` + * - `format` + * - `style` + * - `explode` + * - `enum` + * - `default` + * - `example` + * - `examples` + * - `items` + * - `collectionFormat` + * - `maxItems` + * - `minItems` + * - `uniqueItems` + * - `minimum` + * - `multipleOf` + * - `exclusiveMinimum` + * - `maximum` + * - `exclusiveMaximum` + * - `pattern` + * - `minLength` + * - `maxLength` + * + * @example + * ```typescript + * 'header': (before, after) => { + * return { + * 'x-my-extension': before('description'), + * 'x-my-config': after('schema') + * }; + * } + * ``` + */ + 'header'?: ContextExtensionFunction<'header'>; + /** Link extensions + * + * Available keys: + * - `operationId` + * - `description` + * - `server` + * - `operationRef` + * - `parameters` + * - `requestBody` + * + * @example + * ```typescript + * 'link': (before, after) => { + * return { + * 'x-my-extension': before('operationId'), + * 'x-my-config': after('parameters') + * }; + * } + * ``` + */ + 'link'?: ContextExtensionFunction<'link'>; + /** Example extensions + * + * Available keys: + * - `summary` + * - `description` + * - `value` + * - `externalValue` + * + * @example + * ```typescript + * 'example': (before, after) => { + * return { + * 'x-my-extension': before('summary'), + * 'x-my-config': after('value') + * }; + * } + * ``` + */ + 'example'?: ContextExtensionFunction<'example'>; + /** Discriminator extensions + * + * Available keys: + * - `propertyName` + * - `mapping` + * + * @example + * ```typescript + * 'discriminator': (before, after) => { + * return { + * 'x-my-extension': before('propertyName'), + * 'x-my-config': after('mapping') + * }; + * } + * ``` + */ + 'discriminator'?: ContextExtensionFunction<'discriminator'>; + /** XML extensions + * + * Available keys: + * - `name` + * - `namespace` + * - `prefix` + * - `attribute` + * - `wrapped` + * + * @example + * ```typescript + * 'xml': (before, after) => { + * return { + * 'x-my-extension': before('name'), + * 'x-my-config': after('namespace') + * }; + * } + * ``` + */ + 'xml'?: ContextExtensionFunction<'xml'>; + /** Contact extensions + * + * Available keys: + * - `name` + * - `email` + * - `url` + * + * @example + * ```typescript + * 'contact': (before, after) => { + * return { + * 'x-my-extension': before('name'), + * 'x-my-config': after('email') + * }; + * } + * ``` + */ + 'contact'?: ContextExtensionFunction<'contact'>; + /** License extensions + * + * Available keys: + * - `name` + * - `identifier` + * - `url` + * + * @example + * ```typescript + * 'license': (before, after) => { + * return { + * 'x-my-extension': before('name'), + * 'x-my-config': after('identifier') + * }; + * } + * ``` + */ + 'license'?: ContextExtensionFunction<'license'>; + /** OAuth flow extensions + * + * Available keys: + * - `authorizationUrl` + * - `tokenUrl` + * - `refreshUrl` + * - `scopes` + * + * @example + * ```typescript + * 'oauthFlow': (before, after) => { + * return { + * 'x-my-extension': before('authorizationUrl'), + * 'x-my-config': after('tokenUrl') + * }; + * } + * ``` + */ + 'oauthFlow'?: ContextExtensionFunction<'oauthFlow'>; + /** Server variable extensions + * + * Available keys: + * - `description` + * - `default` + * - `enum` + * + * @example + * ```typescript + * 'serverVariable': (before, after) => { + * return { + * 'x-my-extension': before('description'), + * 'x-my-config': after('default') + * }; + * } + * ``` + */ + 'serverVariable'?: ContextExtensionFunction<'serverVariable'>; } /** @@ -609,6 +919,7 @@ export function createPositionHelpers(context: T) export const KeyMap = { 'top-level': RootKeys, 'info': InfoKeys, + 'components': ComponentsKeys, 'operation': OperationKeys, 'parameter': ParameterKeys, 'schema': SchemaKeys, @@ -618,6 +929,19 @@ export const KeyMap = { 'tag': TagKeys, 'externalDocs': ExternalDocsKeys, 'webhook': WebhookKeys, + 'pathItem': PathItemKeys, + 'requestBody': RequestBodyKeys, + 'mediaType': MediaTypeKeys, + 'encoding': EncodingKeys, + 'header': HeaderKeys, + 'link': LinkKeys, + 'example': ExampleKeys, + 'discriminator': DiscriminatorKeys, + 'xml': XMLKeys, + 'contact': ContactKeys, + 'license': LicenseKeys, + 'oauthFlow': OAuthFlowKeys, + 'serverVariable': ServerVariableKeys, 'definitions': SchemaKeys, 'securityDefinitions': SecuritySchemeKeys, } diff --git a/src/extensions/vendor-loader.ts b/src/extensions/vendor-loader.ts index d8e6808..76b28ae 100644 --- a/src/extensions/vendor-loader.ts +++ b/src/extensions/vendor-loader.ts @@ -4,7 +4,7 @@ * Loads vendor extensions using static imports for ES module compatibility. */ -import { before, after, KeyMap, VendorExtensions } from './index.js'; +import { before, after, type KeyMap, type VendorExtensions } from './index.js'; // Import vendor extensions statically import { speakeasy } from './vendor/speakeasy.js'; diff --git a/src/index.ts b/src/index.ts index ad9d907..a63d35f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,48 +1,45 @@ -import { SupportLanguage, Parser, Printer } from 'prettier'; -import type { Plugin } from 'prettier'; import * as yaml from 'js-yaml'; +import type{ AstPath, Doc, Parser, ParserOptions, Printer, SupportLanguage } from 'prettier'; import { getVendorExtensions } from './extensions/vendor-loader.js'; +export type PrintFn = (path: AstPath) => Doc; + import { - RootKeys, - InfoKeys, - ContactKeys, - LicenseKeys, - ComponentsKeys, - OperationKeys, - ParameterKeys, - SchemaKeys, - ResponseKeys, - SecuritySchemeKeys, - OAuthFlowKeys, - ServerKeys, - ServerVariableKeys, - TagKeys, - ExternalDocsKeys, - WebhookKeys, - PathItemKeys, - RequestBodyKeys, - MediaTypeKeys, - EncodingKeys, - HeaderKeys, - LinkKeys, - ExampleKeys, - DiscriminatorKeys, - XMLKeys, + ComponentsKeys, + ContactKeys, + DiscriminatorKeys, + EncodingKeys, + ExampleKeys, + ExternalDocsKeys, + HeaderKeys, + InfoKeys, + LicenseKeys, + LinkKeys, + MediaTypeKeys, + OAuthFlowKeys, + OperationKeys, + ParameterKeys, + PathItemKeys, + RequestBodyKeys, + ResponseKeys, + RootKeys, + SchemaKeys, + SecuritySchemeKeys, + ServerKeys, + ServerVariableKeys, + TagKeys, + WebhookKeys, + XMLKeys, } from './keys.js'; // Type definitions for better type safety interface OpenAPINode { - type: 'openapi'; + isOpenAPI: boolean; content: any; originalText: string; format: 'json' | 'yaml'; } -interface PrettierPath { - getValue(): OpenAPINode; -} - interface OpenAPIPluginOptions { tabWidth?: number; printWidth?: number; @@ -59,18 +56,22 @@ const vendorExtensions = getVendorExtensions(); * Unified parser that can handle both JSON and YAML OpenAPI files */ function parseOpenAPIFile(text: string, options?: any): OpenAPINode { - // Try to detect the format based on file extension or content - const filePath = options?.filepath || ''; - const isYamlFile = filePath.endsWith('.yaml') || filePath.endsWith('.yml'); - const isJsonFile = filePath.endsWith('.json'); - - // If we can't determine from extension, try to detect from content - let format: 'json' | 'yaml' = 'json'; // default to JSON - if (isYamlFile) { - format = 'yaml'; - } else if (isJsonFile) { - format = 'json'; - } else { + console.debug('parseOpenAPIFile', text, options); + + let format: 'json' | 'yaml' | undefined; + + if (options?.filepath) { + switch (true) { + case options?.filepath.endsWith('.yaml') || options?.filepath.endsWith('.yml'): + format = 'yaml'; + break; + case options?.filepath.endsWith('.json'): + format = 'json'; + break; + } + } + + if (!format) { // Try to detect format from content const trimmedText = text.trim(); if (trimmedText.startsWith('{') || trimmedText.startsWith('[')) { @@ -79,35 +80,61 @@ function parseOpenAPIFile(text: string, options?: any): OpenAPINode { format = 'yaml'; } } - - try { - let parsed: any; - - if (format === 'json') { - parsed = JSON.parse(text); - } else { + + let parsed: any; + + switch (format) { + case 'yaml': + try { parsed = yaml.load(text, { schema: yaml.DEFAULT_SCHEMA, onWarning: (warning) => { // Handle YAML warnings if needed console.warn('YAML parsing warning:', warning); - } - }); - } - - // Check if this is an OpenAPI file - if (!isOpenAPIFile(parsed, filePath)) { - throw new Error('Not an OpenAPI file'); - } - + } + }); + } catch (error) { + throw new Error(`Failed to parse OpenAPI YAML: ${error}`); + } + break; + case 'json': + try { + parsed = JSON.parse(text); + } catch (error) { + throw new Error(`Failed to parse OpenAPI JSON: ${error}`); + } + break; + } + + let isOpenAPI: boolean; + + try { + isOpenAPI = isOpenAPIFile(parsed, options?.filepath); + } catch (error) { return { - type: 'openapi', + isOpenAPI: false, content: parsed, originalText: text, format: format, - }; - } catch (error) { - throw new Error(`Failed to parse OpenAPI ${format.toUpperCase()}: ${error}`); + } + } + + switch (isOpenAPI) { + case true: + return { + isOpenAPI: true, + content: parsed, + originalText: text, + format: format, + } + case false: + default: + return { + isOpenAPI: false, + content: parsed, + originalText: text, + format: format, + } } } @@ -132,17 +159,17 @@ function isOpenAPIFile(content: any, filePath?: string): boolean { // Only accept files in OpenAPI-related directories if (filePath) { const path = filePath.toLowerCase(); - + // Check for component directory patterns - if (path.includes('/components/') || - path.includes('/schemas/') || - path.includes('/parameters/') || - path.includes('/responses/') || - path.includes('/requestbodies/') || - path.includes('/headers/') || - path.includes('/examples/') || - path.includes('/securityschemes/') || - path.includes('/links/') || + if (path.includes('/components/') || + path.includes('/schemas/') || + path.includes('/parameters/') || + path.includes('/responses/') || + path.includes('/requestbodies/') || + path.includes('/headers/') || + path.includes('/examples/') || + path.includes('/securityschemes/') || + path.includes('/links/') || path.includes('/callbacks/') || path.includes('/webhooks/') || path.includes('/paths/')) { @@ -204,11 +231,11 @@ function isOpenAPIFile(content: any, filePath?: string): boolean { // Additional strict check: reject objects that look like generic data // If an object only has simple properties like name, age, etc. without any OpenAPI structure, reject it const keys = Object.keys(content); - const hasOnlyGenericProperties = keys.every(key => + const hasOnlyGenericProperties = keys.every(key => !key.startsWith('x-') && // Not a custom extension !['openapi', 'swagger', 'info', 'paths', 'components', 'definitions', 'parameters', 'responses', 'securityDefinitions', 'tags', 'servers', 'webhooks'].includes(key) ); - + if (hasOnlyGenericProperties) { return false; } @@ -229,36 +256,40 @@ function isPathObject(obj: any): boolean { return Object.keys(obj).some(key => httpMethods.includes(key.toLowerCase())); } -const plugin: Plugin = { - languages: [ - { - name: 'openapi', - extensions: [ - // Accepting all JSON and YAML files so that component files used by $ref work - '.json', '.yaml', '.yml' - ], - parsers: ['openapi-parser'], +export const languages: Partial[] = [ + { + name: 'openapi', + extensions: [ + // Accepting all JSON and YAML files so that component files used by $ref work + '.json', '.yaml', '.yml' + ], + parsers: ['openapi-parser'], + }, +]; + +export const parsers: Record = { + 'openapi-parser': { + parse: (text: string, options?: any): OpenAPINode => { + return parseOpenAPIFile(text, options); }, - ], - parsers: { - 'openapi-parser': { - parse: (text: string, options?: any): OpenAPINode => { - return parseOpenAPIFile(text, options); - }, - astFormat: 'openapi-ast', - locStart: (node: OpenAPINode) => 0, - locEnd: (node: OpenAPINode) => node.originalText?.length || 0, + astFormat: 'openapi-ast', + locStart: (node: OpenAPINode) => 0, + locEnd: (node: OpenAPINode) => node.originalText?.length || 0, + }, +} + +export const printers: Record = { + 'openapi-ast': { + print: (path: AstPath, options: ParserOptions, print: PrintFn): string => { + const node = path.getNode(); + if (!node.isOpenAPI || node.isOpenAPI === false) { + // Return original text unchanged + return options.originalText; + } + return formatOpenAPI(node.content, node.format, options); }, }, - printers: { - 'openapi-ast': { - print: (path: PrettierPath, options?: any, print?: any, ...rest: any[]): string => { - const node = path.getValue(); - return formatOpenAPI(node.content, node.format, options); - }, - }, - }, -}; +} /** * Unified formatter that outputs in the detected format @@ -267,29 +298,21 @@ function formatOpenAPI(content: any, format: 'json' | 'yaml', options?: OpenAPIP // Sort keys for better organization const sortedContent = sortOpenAPIKeys(content); - if (format === 'json') { - // Format with proper indentation - return JSON.stringify(sortedContent, null, options?.tabWidth || 2); - } else { - // Format YAML with proper indentation and line breaks - return yaml.dump(sortedContent, { - indent: options?.tabWidth || 2, - lineWidth: options?.printWidth || 80, - noRefs: true, - quotingType: '"', - forceQuotes: false, - }); + switch (format) { + case 'json': + return JSON.stringify(sortedContent, null, options?.tabWidth || 2); + case 'yaml': + // Format YAML with proper indentation and line breaks + return yaml.dump(sortedContent, { + indent: options?.tabWidth || 2, + lineWidth: options?.printWidth || 80, + noRefs: true, + quotingType: '"', + forceQuotes: false, + }); } } -function formatOpenAPIJSON(content: any, options?: OpenAPIPluginOptions): string { - return formatOpenAPI(content, 'json', options); -} - -function formatOpenAPIYAML(content: any, options?: OpenAPIPluginOptions): string { - return formatOpenAPI(content, 'yaml', options); -} - function sortOpenAPIKeys(obj: any): any { if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { return obj; @@ -316,7 +339,7 @@ function sortOpenAPIKeysEnhanced(obj: any, path: string = ''): any { if (typeof obj !== 'object' || obj === null) { return obj; } - + // Handle arrays by recursively sorting each element if (Array.isArray(obj)) { return obj.map((item, index) => sortOpenAPIKeysEnhanced(item, `${path}[${index}]`)); @@ -389,11 +412,11 @@ function isSchemaObject(obj: any): boolean { if (!obj || typeof obj !== 'object') { return false; } - + // Check for JSON Schema keywords - be very strict const hasSchemaKeywords = '$ref' in obj || 'allOf' in obj || 'oneOf' in obj || 'anyOf' in obj || 'not' in obj; const hasValidType = 'type' in obj && obj.type && ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'].includes(obj.type); - + // Only return true if we have clear schema indicators // Must have either schema keywords OR valid type with schema properties // Also require additional schema-specific properties to be more strict @@ -414,8 +437,8 @@ function isServerObject(obj: any): boolean { } function isTagObject(obj: any): boolean { - return obj && typeof obj === 'object' && 'name' in obj && typeof obj.name === 'string' && - (Object.keys(obj).length === 1 || // Only name + return obj && typeof obj === 'object' && 'name' in obj && typeof obj.name === 'string' && + (Object.keys(obj).length === 1 || // Only name 'description' in obj || // name + description 'externalDocs' in obj); // name + externalDocs } @@ -546,7 +569,7 @@ function getContextKey(path: string, obj: any): string { if (path === 'webhooks') return 'webhook'; if (path === 'definitions') return 'definitions'; if (path === 'securityDefinitions') return 'securityDefinitions'; - + // Check if this is a path operation (e.g., "paths./users.get") if (path.includes('.') && path.split('.').length >= 3) { const pathParts = path.split('.'); @@ -648,6 +671,4 @@ function getStandardKeysForContext(contextKey: string): readonly string[] { case 'securityDefinitions': return SecuritySchemeKeys; // Security definitions use security scheme keys default: return RootKeys; } -} - -export default plugin; +} \ No newline at end of file diff --git a/test/build.test.ts b/test/build.test.ts deleted file mode 100644 index c365d7e..0000000 --- a/test/build.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { describe, expect, it } from 'bun:test'; -import * as fs from 'fs'; -import * as path from 'path'; - -describe('Build Tests', () => { - describe('Build artifacts', () => { - it('should create dist directory', () => { - const distPath = path.join(process.cwd(), 'dist'); - expect(fs.existsSync(distPath)).toBe(true); - }); - - it('should create main index.js file', () => { - const indexPath = path.join(process.cwd(), 'dist', 'index.js'); - expect(fs.existsSync(indexPath)).toBe(true); - }); - - it('should create TypeScript declaration files', () => { - const dtsPath = path.join(process.cwd(), 'dist', 'index.d.ts'); - expect(fs.existsSync(dtsPath)).toBe(true); - }); - - it('should create source map files', () => { - const mapPath = path.join(process.cwd(), 'dist', 'index.js.map'); - expect(fs.existsSync(mapPath)).toBe(true); - }); - - it('should have valid JavaScript in dist/index.js', () => { - const indexPath = path.join(process.cwd(), 'dist', 'index.js'); - const content = fs.readFileSync(indexPath, 'utf-8'); - - // Should not contain TypeScript syntax - expect(content).not.toContain(': string'); - expect(content).not.toContain(': number'); - expect(content).not.toContain('interface '); - // Note: 'type ' might appear in comments or strings, so we check for type annotations instead - expect(content).not.toMatch(/\btype\s+[A-Z]/); - - // Should be valid JavaScript (but may contain ES module syntax) - // We can't use new Function() with ES modules, so we just check it's not empty - expect(content.length).toBeGreaterThan(0); - }); - - it('should export the plugin as default export', () => { - const indexPath = path.join(process.cwd(), 'dist', 'index.js'); - const content = fs.readFileSync(indexPath, 'utf-8'); - - // Should have module.exports (CommonJS format) - expect(content).toContain('module.exports'); - }); - - it('should have proper module structure', () => { - const indexPath = path.join(process.cwd(), 'dist', 'index.js'); - const content = fs.readFileSync(indexPath, 'utf-8'); - - // Should be CommonJS module - expect(content).toContain('require'); - expect(content).toContain('module.exports'); - }); - }); - - describe('Package.json validation', () => { - it('should have correct main field', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - expect(packageJson.main).toBe('dist/index.js'); - }); - - it('should have correct types field', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - expect(packageJson.types).toBe('./dist/index.d.ts'); - }); - - it('should have correct exports field', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - expect(packageJson.exports).toBeDefined(); - expect(packageJson.exports['.']).toBeDefined(); - expect(packageJson.exports['.'].default).toBe('./dist/index.js'); - }); - - it('should include required files in files array', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - expect(packageJson.files).toContain('dist/index.js'); - expect(packageJson.files).toContain('dist/index.d.ts'); - }); - - it('should have required metadata', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - - expect(packageJson.name).toBeDefined(); - expect(packageJson.version).toBeDefined(); - expect(packageJson.description).toBeDefined(); - expect(packageJson.author).toBeDefined(); - expect(packageJson.license).toBeDefined(); - expect(packageJson.keywords).toBeDefined(); - expect(packageJson.repository).toBeDefined(); - expect(packageJson.bugs).toBeDefined(); - expect(packageJson.homepage).toBeDefined(); - }); - - it('should have correct peer dependencies', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - expect(packageJson.peerDependencies).toBeDefined(); - expect(packageJson.peerDependencies.prettier).toBeDefined(); - }); - - it('should have correct engines requirement', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - expect(packageJson.engines).toBeDefined(); - expect(packageJson.engines.node).toBe('>=18.0.0'); - }); - }); - - describe('NPM package validation', () => { - it('should have all required files for npm publish', () => { - const requiredFiles = [ - 'dist/index.js', - 'dist/index.d.ts', - 'dist/index.js.map', - 'README.md', - 'package.json' - ]; - - requiredFiles.forEach(file => { - const filePath = path.join(process.cwd(), file); - expect(fs.existsSync(filePath)).toBe(true); - }); - }); - - it('should not include development files in npm package', () => { - const excludedFiles = [ - 'src/', - 'test/', - '.github/', - '.husky/', - '.eslintrc.js', - '.prettierrc.js', - 'tsconfig.json', - 'bunfig.toml' - ]; - - // These files should not be in the npm package - // (This is handled by .npmignore, but we can verify the ignore file exists) - expect(fs.existsSync('.npmignore')).toBe(true); - }); - - it('should have valid package.json for npm', () => { - const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); - - // Required fields for npm - expect(packageJson.name).toBeTruthy(); - expect(packageJson.version).toBeTruthy(); - expect(packageJson.main).toBeTruthy(); - expect(packageJson.license).toBeTruthy(); - - // Should not have private field (or it should be false) - if (packageJson.private !== undefined) { - expect(packageJson.private).toBe(false); - } - }); - }); - - describe('TypeScript compilation', () => { - it('should compile without errors', () => { - // This test assumes the build has already been run - // In a real scenario, you might want to run tsc programmatically - const distPath = path.join(process.cwd(), 'dist'); - expect(fs.existsSync(distPath)).toBe(true); - }); - - it('should generate declaration files', () => { - const dtsPath = path.join(process.cwd(), 'dist', 'index.d.ts'); - const content = fs.readFileSync(dtsPath, 'utf-8'); - - // Should contain type declarations - expect(content).toContain('declare'); - expect(content).toContain('export'); - }); - - it('should generate source maps', () => { - const mapPath = path.join(process.cwd(), 'dist', 'index.js.map'); - const content = fs.readFileSync(mapPath, 'utf-8'); - const sourceMap = JSON.parse(content); - - // Should be valid source map - expect(sourceMap.version).toBeDefined(); - expect(sourceMap.sources).toBeDefined(); - expect(sourceMap.mappings).toBeDefined(); - }); - }); -}); diff --git a/test/coverage.test.ts b/test/coverage.test.ts index f5effab..fbae22f 100644 --- a/test/coverage.test.ts +++ b/test/coverage.test.ts @@ -1,79 +1,10 @@ import { describe, expect, it } from 'bun:test'; -import plugin from '../src/index'; +import { parsers, printers } from '../src/index'; describe('Coverage Tests', () => { - describe('Error handling and edge cases', () => { - it('should handle null and undefined content', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); - - // Test with null content - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('null', { filepath: 'test.json' }); - }).toThrow(); - - // Test with undefined content - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('undefined', { filepath: 'test.json' }); - }).toThrow(); - }); - - it('should handle non-object content', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); - - // Test with string content - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('"string"', { filepath: 'test.json' }); - }).toThrow(); - - // Test with number content - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('123', { filepath: 'test.json' }); - }).toThrow(); - }); - - it('should handle array content', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); - - // Test with array content - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('[]', { filepath: 'test.json' }); - }).toThrow(); - }); - - it('should handle malformed JSON', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); - - // Test with malformed JSON - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('{invalid json}', { filepath: 'test.json' }); - }).toThrow('Failed to parse OpenAPI JSON'); - }); - - it('should handle malformed YAML', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); - - // Test with malformed YAML - expect(() => { - // @ts-expect-error We are testing edge cases - parser?.parse('invalid: yaml: content:', { filepath: 'test.yaml' }); - }).toThrow('Failed to parse OpenAPI YAML'); - }); - }); - describe('File path detection', () => { it('should detect OpenAPI files in component directories', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testYaml = `type: object @@ -100,13 +31,13 @@ properties: // @ts-expect-error We are testing edge cases const result = parser?.parse(testYaml, { filepath: path }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); expect(result?.format).toBe('yaml'); }); }); it('should handle files without filepath', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testYaml = `openapi: 3.0.0 @@ -117,13 +48,13 @@ info: // @ts-expect-error We are testing edge cases const result = parser?.parse(testYaml, { filepath: 'test.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); }); describe('Object type detection', () => { it('should detect operation objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const operationYaml = `get: @@ -135,11 +66,11 @@ info: // @ts-expect-error We are testing edge cases const result = parser?.parse(operationYaml, { filepath: 'paths/users.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect parameter objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const parameterYaml = `name: id @@ -151,11 +82,11 @@ schema: // @ts-expect-error We are testing edge cases const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect schema objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const schemaYaml = `type: object @@ -170,11 +101,11 @@ required: // @ts-expect-error We are testing edge cases const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect response objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const responseYaml = `description: User response @@ -186,11 +117,11 @@ content: // @ts-expect-error We are testing edge cases const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect security scheme objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const securityYaml = `type: http @@ -200,11 +131,11 @@ bearerFormat: JWT`; // @ts-expect-error We are testing edge cases const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect server objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const serverYaml = `url: https://api.example.com @@ -213,11 +144,11 @@ description: Production server`; // @ts-expect-error We are testing edge cases const result = parser?.parse(serverYaml, { filepath: 'servers/production.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect tag objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const tagYaml = `name: users @@ -226,11 +157,11 @@ description: User management operations`; // @ts-expect-error We are testing edge cases const result = parser?.parse(tagYaml, { filepath: 'tags/users.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect external docs objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const externalDocsYaml = `url: https://example.com/docs @@ -239,11 +170,11 @@ description: External documentation`; // @ts-expect-error We are testing edge cases const result = parser?.parse(externalDocsYaml, { filepath: 'externalDocs/api.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); it('should detect webhook objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const webhookYaml = `post: @@ -255,17 +186,17 @@ description: External documentation`; // @ts-expect-error We are testing edge cases const result = parser?.parse(webhookYaml, { filepath: 'webhooks/messageCreated.yaml' }); expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); + expect(result?.isOpenAPI).toBeTrue(); }); }); describe('Sorting functions', () => { it('should handle path sorting by specificity', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -280,9 +211,9 @@ description: External documentation`; }; // @ts-expect-error We are testing edge cases - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); - + if (result && typeof result === 'string') { const formatted = JSON.parse(result); const pathKeys = Object.keys(formatted.paths); @@ -294,11 +225,11 @@ description: External documentation`; }); it('should handle response code sorting', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -319,9 +250,9 @@ description: External documentation`; }; // @ts-expect-error We are testing edge cases - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); - + if (result && typeof result === 'string') { const formatted = JSON.parse(result); const responseKeys = Object.keys(formatted.paths['/test'].get.responses); @@ -335,11 +266,11 @@ description: External documentation`; describe('Context key detection', () => { it('should handle nested path contexts', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -360,9 +291,9 @@ description: External documentation`; }; // @ts-expect-error We are testing edge cases - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); - + if (result && typeof result === 'string') { const formatted = JSON.parse(result); expect(formatted.components.schemas.User).toBeDefined(); diff --git a/test/custom-extensions.test.ts b/test/custom-extensions.test.ts index adf90a4..1641578 100644 --- a/test/custom-extensions.test.ts +++ b/test/custom-extensions.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'bun:test'; -import plugin from '../src/index'; +import {parsers, printers} from '../src/index'; describe('Custom Extensions Support', () => { it('should handle custom extensions in top-level keys', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testJson = { @@ -21,7 +21,7 @@ describe('Custom Extensions Support', () => { }); it('should handle custom extensions in info section', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testJson = { @@ -43,7 +43,7 @@ describe('Custom Extensions Support', () => { }); it('should handle custom extensions in operation objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testJson = { @@ -68,7 +68,7 @@ describe('Custom Extensions Support', () => { }); it('should handle custom extensions in schema objects', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testJson = { @@ -95,10 +95,12 @@ describe('Custom Extensions Support', () => { }); it('should format JSON with custom extensions', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { 'x-custom-field': 'value', 'openapi': '3.0.0', @@ -109,17 +111,19 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); expect(result).toContain('x-custom-field'); expect(result).toContain('openapi'); }); it('should format YAML with custom extensions', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { 'x-custom-field': 'value', 'openapi': '3.0.0', @@ -130,14 +134,14 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); expect(result).toContain('x-custom-field:'); expect(result).toContain('openapi:'); }); it('should handle unknown keys alphabetically at the end', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const testJson = { @@ -156,10 +160,12 @@ describe('Custom Extensions Support', () => { describe('Custom extension positioning', () => { it('should position custom extensions correctly in top-level', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { 'x-custom-field': 'value', 'openapi': '3.0.0', @@ -170,7 +176,7 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -193,10 +199,12 @@ describe('Custom Extensions Support', () => { }); it('should position custom extensions correctly in info section', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { 'openapi': '3.0.0', 'info': { @@ -210,7 +218,7 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -233,10 +241,12 @@ describe('Custom Extensions Support', () => { }); it('should position custom extensions correctly in operation objects', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { 'openapi': '3.0.0', 'info': { 'title': 'Test API', 'version': '1.0.0' }, @@ -254,7 +264,7 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -274,10 +284,12 @@ describe('Custom Extensions Support', () => { }); it('should position custom extensions correctly in schema objects', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { 'openapi': '3.0.0', 'info': { 'title': 'Test API', 'version': '1.0.0' }, @@ -295,7 +307,7 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -318,10 +330,12 @@ describe('Custom Extensions Support', () => { describe('Unknown key handling', () => { it('should sort unknown keys alphabetically at the end', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { 'openapi': '3.0.0', 'info': { 'title': 'Test API', 'version': '1.0.0' }, @@ -332,7 +346,7 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -358,10 +372,12 @@ describe('Custom Extensions Support', () => { }); it('should handle mixed custom extensions and unknown keys', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { 'openapi': '3.0.0', 'info': { 'title': 'Test API', 'version': '1.0.0' }, @@ -374,7 +390,7 @@ describe('Custom Extensions Support', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { diff --git a/test/file-detection.test.ts b/test/file-detection.test.ts index bdf5754..49ae2f7 100644 --- a/test/file-detection.test.ts +++ b/test/file-detection.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it } from 'bun:test'; -import plugin from '../src/index'; +import { parsers } from '../src/index'; describe('File Detection Tests', () => { - it('should detect OpenAPI root files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + it('should detect OpenAPI root files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); - const testYaml = `openapi: 3.0.0 + const testYaml = `openapi: 3.0.0 info: title: Test API version: 1.0.0 @@ -17,20 +17,20 @@ paths: '200': description: Success`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(testYaml, { filepath: 'openapi.yaml' }); - - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - expect(result?.content.openapi).toBe('3.0.0'); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(testYaml, { filepath: 'openapi.yaml' }); - it('should detect partial schema files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + expect(result?.content.openapi).toBe('3.0.0'); + }); - const schemaYaml = `type: object + it('should detect partial schema files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const schemaYaml = `type: object properties: id: type: integer @@ -40,40 +40,40 @@ required: - id - name`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' }); - - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - expect(result?.content.type).toBe('object'); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(schemaYaml, { filepath: 'components/schemas/User.yaml' }); - it('should detect parameter files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + expect(result?.content.type).toBe('object'); + }); - const parameterYaml = `name: id + it('should detect parameter files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const parameterYaml = `name: id in: path required: true description: User ID schema: type: integer`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' }); - - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - expect(result?.content.name).toBe('id'); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(parameterYaml, { filepath: 'components/parameters/UserId.yaml' }); - it('should detect response files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + expect(result?.content.name).toBe('id'); + }); - const responseYaml = `description: User response + it('should detect response files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const responseYaml = `description: User response content: application/json: schema: @@ -84,20 +84,20 @@ content: name: type: string`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' }); - - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - expect(result?.content.description).toBe('User response'); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(responseYaml, { filepath: 'components/responses/UserResponse.yaml' }); - it('should detect path files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + expect(result?.content.description).toBe('User response'); + }); - const pathYaml = `get: + it('should detect path files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const pathYaml = `get: summary: Get users responses: '200': @@ -110,91 +110,100 @@ post: schema: type: object`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(pathYaml, { filepath: 'paths/users.yaml' }); - - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - expect(result?.content.get).toBeDefined(); - expect(result?.content.post).toBeDefined(); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(pathYaml, { filepath: 'paths/users.yaml' }); - it('should detect security scheme files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + expect(result?.content.get).toBeDefined(); + expect(result?.content.post).toBeDefined(); + }); - const securityYaml = `type: http + it('should detect security scheme files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const securityYaml = `type: http scheme: bearer bearerFormat: JWT description: JWT authentication`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' }); - - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - expect(result?.content.type).toBe('http'); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(securityYaml, { filepath: 'components/securitySchemes/BearerAuth.yaml' }); - it('should reject non-OpenAPI files', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + expect(result?.content.type).toBe('http'); + }); - const nonOpenAPIYaml = `name: John + it('should handle non-OpenAPI files', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const nonOpenAPIYaml = `name: John age: 30 city: New York`; - // @ts-expect-error We are mocking things here - expect(() => parser?.parse(nonOpenAPIYaml, { filepath: 'config/data.yaml' })).toThrow('Not an OpenAPI file'); - }); + const parsedYaml = { + name: 'John', + age: 30, + city: 'New York' + } - it('should accept files in OpenAPI directories even with simple content', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + // @ts-expect-error We are mocking things here + const parsedData = parser?.parse(nonOpenAPIYaml, { filepath: 'config/data.yaml' }) - const simpleYaml = `name: John + expect(parsedData).toBeDefined() + expect(parsedData?.isOpenAPI).toBeFalse(); + }); + + it('should accept files in OpenAPI directories even with simple content', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); + + const simpleYaml = `name: John age: 30 city: New York`; - // @ts-expect-error We are mocking things here - const result = parser?.parse(simpleYaml, { filepath: 'components/schemas/User.yaml' }); - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); - }); + // @ts-expect-error We are mocking things here + const result = parser?.parse(simpleYaml, { filepath: 'components/schemas/User.yaml' }); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + }); - it('should support component directory patterns', () => { - const parser = plugin.parsers?.['openapi-parser']; - expect(parser).toBeDefined(); + it('should support component directory patterns', () => { + const parser = parsers?.['openapi-parser']; + expect(parser).toBeDefined(); - const componentYaml = `type: object + const componentYaml = `type: object properties: message: type: string`; - // Test various component directory patterns - const paths = [ - 'components/schemas/Error.yaml', - 'components/parameters/CommonPagination.yaml', - 'components/responses/ErrorResponse.yaml', - 'components/requestBodies/UserCreateBody.yaml', - 'components/headers/RateLimitHeaders.yaml', - 'components/examples/UserExample.yaml', - 'components/securitySchemes/BearerAuth.yaml', - 'components/links/UserCreatedLink.yaml', - 'components/callbacks/NewMessageCallback.yaml', - 'webhooks/messageCreated.yaml', - 'paths/users.yaml' - ]; + // Test various component directory patterns + const paths = [ + 'components/schemas/Error.yaml', + 'components/parameters/CommonPagination.yaml', + 'components/responses/ErrorResponse.yaml', + 'components/requestBodies/UserCreateBody.yaml', + 'components/headers/RateLimitHeaders.yaml', + 'components/examples/UserExample.yaml', + 'components/securitySchemes/BearerAuth.yaml', + 'components/links/UserCreatedLink.yaml', + 'components/callbacks/NewMessageCallback.yaml', + 'webhooks/messageCreated.yaml', + 'paths/users.yaml' + ]; - paths.forEach(path => { - // @ts-expect-error We are mocking things here - const result = parser?.parse(componentYaml, { filepath: path }); - expect(result).toBeDefined(); - expect(result?.type).toBe('openapi'); - expect(result?.format).toBe('yaml'); + paths.forEach(path => { + // @ts-expect-error We are mocking things here + const result = parser?.parse(componentYaml, { filepath: path }); + expect(result).toBeDefined(); + expect(result?.isOpenAPI).toBeTrue(); + expect(result?.format).toBe('yaml'); + }); }); - }); }); diff --git a/test/integration.test.ts b/test/integration.test.ts index 56d0803..ae8d5ad 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'bun:test'; -import plugin from '../src/index'; +import {parsers, printers} from '../src/index'; describe('Integration Tests', () => { describe('Real OpenAPI file processing', () => { @@ -213,18 +213,18 @@ describe('Integration Tests', () => { ] }; - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: openApiContent, originalText: '', format: 'json' }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (result && typeof result === 'string') { @@ -365,18 +365,18 @@ describe('Integration Tests', () => { ] }; - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: swaggerContent, originalText: '', format: 'json' }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (result && typeof result === 'string') { @@ -419,18 +419,18 @@ describe('Integration Tests', () => { } }; - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: yamlContent, originalText: '', format: 'yaml' }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (result) { @@ -451,7 +451,7 @@ describe('Integration Tests', () => { describe('Error handling', () => { it('should handle malformed JSON gracefully', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const malformedJson = '{"openapi": "3.0.0", "info": {'; @@ -461,7 +461,7 @@ describe('Integration Tests', () => { }); it('should handle malformed YAML gracefully', () => { - const parser = plugin.parsers?.['openapi-parser']; + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const malformedYaml = 'openapi: 3.0.0\ninfo:\n title: Test\n version: 1.0.0\n invalid: ['; @@ -470,14 +470,23 @@ describe('Integration Tests', () => { expect(() => parser?.parse(malformedYaml, { filepath: 'test.yaml' })).toThrow(); }); - it('should reject non-OpenAPI content', () => { - const parser = plugin.parsers?.['openapi-parser']; + it('should handle non-OpenAPI content', () => { + const parser = parsers?.['openapi-parser']; expect(parser).toBeDefined(); const nonOpenAPI = '{"name": "John", "age": 30}'; + const parsedJSON = { + name: 'John', + age: 30 + } + // @ts-expect-error We are mocking things here - expect(() => parser?.parse(nonOpenAPI, { filepath: 'test.json' })).toThrow('Not an OpenAPI file'); + const parsedData = parser?.parse(nonOpenAPI, { filepath: 'test.json' }) + expect(parsedData).toBeDefined(); + expect(parsedData?.isOpenAPI).toBeFalse(); + expect(parsedData?.content).toBeDefined(); + expect(parsedData?.content).toEqual(parsedJSON); }); }); @@ -506,11 +515,11 @@ describe('Integration Tests', () => { }; } - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, content: largeOpenAPI, originalText: '', format: 'json' @@ -519,7 +528,7 @@ describe('Integration Tests', () => { const startTime = Date.now(); // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); const endTime = Date.now(); const duration = endTime - startTime; diff --git a/test/key-ordering.test.ts b/test/key-ordering.test.ts index 0d99c0e..c7cc6af 100644 --- a/test/key-ordering.test.ts +++ b/test/key-ordering.test.ts @@ -1,14 +1,15 @@ import { describe, it, expect } from 'bun:test'; -import plugin from '../src/index'; +import { printers } from '../src/index'; describe('Key Ordering Tests', () => { describe('Info section key ordering', () => { it('should sort info keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { @@ -21,10 +22,9 @@ describe('Key Ordering Tests', () => { } }, originalText: '', - format: 'json' }; -// @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + // @ts-expect-error We are mocking things here so we don't need to pass a print function + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -32,7 +32,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + // Check that info keys appear in the correct order const titleIndex = resultString.indexOf('title'); const versionIndex = resultString.indexOf('version'); @@ -51,11 +51,12 @@ describe('Key Ordering Tests', () => { describe('Operation key ordering', () => { it('should sort operation keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { - type: 'openapi', + isOpenAPI: true, + format: 'yaml', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -77,12 +78,11 @@ describe('Key Ordering Tests', () => { } } }, - originalText: '', - format: 'json' + originalText: '' }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -90,7 +90,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + // Check that operation keys appear in the correct order const summaryIndex = resultString.indexOf('summary'); const operationIdIndex = resultString.indexOf('operationId'); @@ -119,10 +119,12 @@ describe('Key Ordering Tests', () => { describe('Schema key ordering', () => { it('should sort schema keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -169,7 +171,8 @@ describe('Key Ordering Tests', () => { }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); + console.log('result', result); expect(result).toBeDefined(); if (!result) { @@ -177,7 +180,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + const typeIndex = resultString.indexOf('type:'); const formatIndex = resultString.indexOf('format:'); const titleIndex = resultString.indexOf('title:'); @@ -247,10 +250,12 @@ describe('Key Ordering Tests', () => { describe('Response key ordering', () => { it('should sort response keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -271,7 +276,7 @@ describe('Key Ordering Tests', () => { } }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -294,10 +299,12 @@ describe('Key Ordering Tests', () => { describe('Parameter key ordering', () => { it('should sort parameter keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -327,7 +334,7 @@ describe('Key Ordering Tests', () => { } }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -335,7 +342,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + const nameIndex = resultString.indexOf('name:'); const inIndex = resultString.indexOf('in:'); const descriptionIndex = resultString.indexOf('description:'); @@ -366,10 +373,12 @@ describe('Key Ordering Tests', () => { describe('Security scheme key ordering', () => { it('should sort security scheme keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -395,7 +404,7 @@ describe('Key Ordering Tests', () => { } }; // @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -426,10 +435,12 @@ describe('Key Ordering Tests', () => { describe('Server key ordering', () => { it('should sort server keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -442,8 +453,8 @@ describe('Key Ordering Tests', () => { ] } }; -// @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + // @ts-expect-error We are mocking things here so we don't need to pass a print function + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -451,7 +462,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + const nameIndex = resultString.indexOf('name'); const descriptionIndex = resultString.indexOf('description'); const urlIndex = resultString.indexOf('url'); @@ -465,10 +476,12 @@ describe('Key Ordering Tests', () => { describe('Tag key ordering', () => { it('should sort tag keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -481,8 +494,8 @@ describe('Key Ordering Tests', () => { ] } }; -// @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + // @ts-expect-error We are mocking things here so we don't need to pass a print function + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -490,7 +503,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + const nameIndex = resultString.indexOf('name'); const descriptionIndex = resultString.indexOf('description'); const externalDocsIndex = resultString.indexOf('externalDocs'); @@ -502,10 +515,12 @@ describe('Key Ordering Tests', () => { describe('External docs key ordering', () => { it('should sort external docs keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -515,8 +530,8 @@ describe('Key Ordering Tests', () => { } } }; -// @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + // @ts-expect-error We are mocking things here so we don't need to pass a print function + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -524,7 +539,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + // Check that external docs keys appear in the correct order const descriptionIndex = resultString.indexOf('description'); const urlIndex = resultString.indexOf('url'); @@ -535,10 +550,12 @@ describe('Key Ordering Tests', () => { describe('Path sorting', () => { it('should sort paths by specificity', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -550,8 +567,8 @@ describe('Key Ordering Tests', () => { } } }; -// @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + // @ts-expect-error We are mocking things here so we don't need to pass a print function + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -559,7 +576,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + // Check that paths are sorted by specificity (fewer parameters first) const usersIndex = resultString.indexOf('/users'); const usersIdIndex = resultString.indexOf('/users/{id}'); @@ -574,10 +591,12 @@ describe('Key Ordering Tests', () => { describe('Response code sorting', () => { it('should sort response codes numerically', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -596,8 +615,8 @@ describe('Key Ordering Tests', () => { } } }; -// @ts-expect-error We are mocking things here so we don't need to pass a print function - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }); + // @ts-expect-error We are mocking things here so we don't need to pass a print function + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }); expect(result).toBeDefined(); if (!result) { @@ -605,7 +624,7 @@ describe('Key Ordering Tests', () => { } const resultString = result.toString(); - + // Check that response codes are sorted numerically const code200Index = resultString.indexOf('200'); const code400Index = resultString.indexOf('400'); diff --git a/test/options.test.ts b/test/options.test.ts deleted file mode 100644 index 6a4a240..0000000 --- a/test/options.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, it, expect } from 'bun:test'; -import plugin from '../src/index'; - -describe('Plugin Options', () => { - it('should use custom tabWidth for JSON formatting', () => { - const testData = { - openapi: '3.0.0', - info: { - title: 'Test', - version: '1.0.0' - }, - paths: {} - }; - - const printer = plugin.printers?.['openapi-ast']; - expect(printer).toBeDefined(); - - const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'json' }) }, { tabWidth: 4 }, () => ''); - expect(result).toBeDefined(); - - if (!result) { - throw new Error('Result is undefined'); - } - - // Check that 4-space indentation is used - expect(result).toContain(' "openapi"'); - expect(result).toContain(' "info"'); - expect(result).toContain(' "title"'); - expect(result).toContain(' "version"'); - }); - - it('should use custom tabWidth for YAML formatting', () => { - const testData = { - openapi: '3.0.0', - info: { - title: 'Test', - version: '1.0.0' - }, - paths: {} - }; - - const printer = plugin.printers?.['openapi-ast']; - expect(printer).toBeDefined(); - - const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'yaml' }) }, { tabWidth: 4 }, () => ''); - expect(result).toBeDefined(); - - if (!result) { - throw new Error('Result is undefined'); - } - - // Check that 4-space indentation is used - expect(result).toContain(' title: Test'); - expect(result).toContain(' version: 1.0.0'); - }); - - it('should use default tabWidth when not specified', () => { - const testData = { - openapi: '3.0.0', - info: { - title: 'Test', - version: '1.0.0' - }, - paths: {} - }; - - const printer = plugin.printers?.['openapi-ast']; - expect(printer).toBeDefined(); - - const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'json' }) }, {}, () => ''); - expect(result).toBeDefined(); - - if (!result) { - throw new Error('Result is undefined'); - } - - // Check that 2-space indentation is used (default) - expect(result).toContain(' "openapi"'); - expect(result).toContain(' "info"'); - expect(result).toContain(' "title"'); - expect(result).toContain(' "version"'); - }); - - it('should use custom printWidth for YAML formatting', () => { - const testData = { - openapi: '3.0.0', - info: { - title: 'This is a very long title that should be wrapped according to printWidth', - version: '1.0.0' - }, - paths: {} - }; - - const printer = plugin.printers?.['openapi-ast']; - expect(printer).toBeDefined(); - - const result = printer?.print({ getValue: () => ({ type: 'openapi', content: testData, originalText: '', format: 'yaml' }) }, { printWidth: 20 }, () => ''); - expect(result).toBeDefined(); - - // The YAML should be formatted with the custom line width - expect(result).toBeDefined(); - // Note: js-yaml doesn't always respect lineWidth for all content types, - // but we can verify the option is passed through - }); -}); diff --git a/test/plugin.test.ts b/test/plugin.test.ts index 2afa3ba..09190ce 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -32,13 +32,13 @@ describe('Prettier OpenAPI Plugin', () => { // @ts-expect-error We are mocking things here const parsed = parser?.parse(inputJson, { filepath: 'test.json' }); expect(parsed).toBeDefined(); - expect(parsed?.type).toBe('openapi'); + expect(parsed?.isOpenAPI).toBeTrue(); expect(parsed?.format).toBe('json'); expect(parsed?.content).toBeDefined(); // Format the parsed content // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); expect(result).toContain('"openapi"'); @@ -80,13 +80,13 @@ openapi: 3.0.0`; // @ts-expect-error We are mocking things here const parsed = parser?.parse(inputYaml, { filepath: 'test.yaml' }); expect(parsed).toBeDefined(); - expect(parsed?.type).toBe('openapi'); + expect(parsed?.isOpenAPI).toBeTrue(); expect(parsed?.format).toBe('yaml'); expect(parsed?.content).toBeDefined(); // Format the parsed content // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); expect(result).toContain('openapi:'); @@ -148,13 +148,13 @@ openapi: 3.0.0`; // @ts-expect-error We are mocking things here const parsed = parser?.parse(inputJson, { filepath: 'test.json' }); expect(parsed).toBeDefined(); - expect(parsed?.type).toBe('openapi'); + expect(parsed?.isOpenAPI).toBeTrue(); expect(parsed?.format).toBe('json'); expect(parsed?.content).toBeDefined(); // Format the parsed content // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); expect(result).toContain('"swagger"'); @@ -201,13 +201,13 @@ describe('Key Ordering Tests', () => { // @ts-expect-error We are mocking things here const parsed = parser?.parse(inputJson, { filepath: 'test.json' }); expect(parsed).toBeDefined(); - expect(parsed?.type).toBe('openapi'); + expect(parsed?.isOpenAPI).toBeTrue(); expect(parsed?.format).toBe('json'); expect(parsed?.content).toBeDefined(); // Format the parsed content // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); @@ -261,13 +261,13 @@ describe('Key Ordering Tests', () => { // @ts-expect-error We are mocking things here const parsed = parser?.parse(inputJson, { filepath: 'test.json' }); expect(parsed).toBeDefined(); - expect(parsed?.type).toBe('openapi'); + expect(parsed?.isOpenAPI).toBeTrue(); expect(parsed?.format).toBe('json'); expect(parsed?.content).toBeDefined(); // Format the parsed content // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => parsed }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => parsed }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); diff --git a/test/simple-ordering.test.ts b/test/simple-ordering.test.ts index ecbc1d6..5b1d8fa 100644 --- a/test/simple-ordering.test.ts +++ b/test/simple-ordering.test.ts @@ -1,12 +1,14 @@ import { describe, expect, it } from 'bun:test'; -import plugin from '../src/index'; +import { printers } from '../src/index'; describe('Simple Key Ordering Tests', () => { it('should sort top-level OpenAPI keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'yaml', content: { paths: { '/test': { get: {} } }, components: { schemas: {} }, @@ -19,7 +21,7 @@ describe('Simple Key Ordering Tests', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -46,10 +48,12 @@ describe('Simple Key Ordering Tests', () => { }); it('should sort operation keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, @@ -74,7 +78,7 @@ describe('Simple Key Ordering Tests', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -109,10 +113,12 @@ describe('Simple Key Ordering Tests', () => { }); it('should sort info keys correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { openapi: '3.0.0', info: { @@ -127,7 +133,7 @@ describe('Simple Key Ordering Tests', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); expect(result).toBeDefined(); if (!result) { @@ -135,8 +141,6 @@ describe('Simple Key Ordering Tests', () => { } const resultString = result.toString(); - - console.log(resultString); // Check that info keys appear in the correct order const titleIndex = resultString.indexOf('title'); @@ -154,10 +158,12 @@ describe('Simple Key Ordering Tests', () => { }); it('should handle custom extensions correctly', () => { - const printer = plugin.printers?.['openapi-ast']; + const printer = printers?.['openapi-ast']; expect(printer).toBeDefined(); const testData = { + isOpenAPI: true, + format: 'json', content: { 'x-custom-field': 'value', 'openapi': '3.0.0', @@ -168,7 +174,8 @@ describe('Simple Key Ordering Tests', () => { }; // @ts-expect-error We are mocking things here - const result = printer?.print({ getValue: () => testData }, { tabWidth: 2 }, () => ''); + const result = printer?.print({ getNode: () => testData }, { tabWidth: 2 }, () => ''); + console.log('result', result); expect(result).toBeDefined(); if (!result) {