addons/docs/src/lib/jsdocParser.test.ts
import { parseJsDoc } from './jsdocParser';
describe('parseJsDoc', () => {
it('should set includesJsDoc to false when the value is null', () => {
const { includesJsDoc, description, extractedTags } = parseJsDoc(null);
expect(includesJsDoc).toBeFalsy();
expect(description).toBeUndefined();
expect(extractedTags).toBeUndefined();
});
it('should set includesJsDocto to false when the value dont contains JSDoc', () => {
const { includesJsDoc, description, extractedTags } = parseJsDoc('Hey!');
expect(includesJsDoc).toBeFalsy();
expect(description).toBeUndefined();
expect(extractedTags).toBeUndefined();
});
it('should set includesJsDoc to true when the value contains JSDoc', () => {
const { includesJsDoc } = parseJsDoc('Hey!\n@version 1.2');
expect(includesJsDoc).toBeTruthy();
});
it('should remove all JSDoc tags from the description', () => {
const { description } = parseJsDoc('Hey!\n@version 1.2\n@deprecated');
expect(description).toBe('Hey!');
});
describe('@ignore', () => {
it('should set ignore to true when @ignore is present', () => {
const { ignore, description, extractedTags } = parseJsDoc('Hey!\n@ignore');
expect(ignore).toBeTruthy();
expect(description).toBeUndefined();
expect(extractedTags).toBeUndefined();
});
it('should set ignore to false when @ignore is not present', () => {
const { ignore } = parseJsDoc('Hey!\n@version 1.2');
expect(ignore).toBeFalsy();
});
});
describe('@param', () => {
it('should ignore invalid @param tags', () => {
const { extractedTags } = parseJsDoc('@param');
expect(extractedTags.params).toBeNull();
});
it('should return a @param with a name', () => {
const { extractedTags } = parseJsDoc('@param event');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).toBeNull();
expect(extractedTags.params[0].description).toBeNull();
});
it('should return a @param with a name and a type', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).not.toBeNull();
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBeNull();
});
it('should return a @param with a name, a type and a description', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event - React event');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).not.toBeNull();
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBe('React event');
});
it('should support multiple @param tags', () => {
const { extractedTags } = parseJsDoc(
'@param {SyntheticEvent} event1 - React event\n@param {SyntheticEvent} event2 - React event\n@param {SyntheticEvent} event3 - React event'
);
['event1', 'event2', 'event3'].forEach((x, i) => {
expect(extractedTags.params[i].name).toBe(x);
expect(extractedTags.params[i].type).not.toBeNull();
expect(extractedTags.params[i].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[i].description).toBe('React event');
});
});
it('should not return extra @param', () => {
const { extractedTags } = parseJsDoc('@param event');
expect(Object.keys(extractedTags.params).length).toBe(1);
});
it('should support multiline description when there is a @param', () => {
const { description, extractedTags } = parseJsDoc(
'This is a\nmultiline description\n@param event'
);
expect(description).toBe('This is a\nmultiline description');
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
});
it('should support multiline @param description', () => {
const { extractedTags } = parseJsDoc(
'@param event - This is a\nmultiline description\n@param anotherEvent'
);
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].description).toBe('This is a\nmultiline description');
expect(extractedTags.params[1].name).toBe('anotherEvent');
});
['@arg', '@argument'].forEach((x) => {
it(`should support ${x} alias`, () => {
const { extractedTags } = parseJsDoc(`${x} {SyntheticEvent} event - React event`);
expect(extractedTags.params).not.toBeNull();
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type).not.toBeNull();
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBe('React event');
});
});
describe('getTypeName', () => {
it('should support record type with a single field', () => {
const { extractedTags } = parseJsDoc('@param {{a: number}} event');
expect(extractedTags.params[0].getTypeName()).toBe('({a: number})');
});
it('should support record type with multiple fields', () => {
const { extractedTags } = parseJsDoc('@param {{a: number, b: string}} event');
expect(extractedTags.params[0].getTypeName()).toBe('({a: number, b: string})');
});
it('should support record type with a field having only a name', () => {
const { extractedTags } = parseJsDoc('@param {{a}} event');
expect(extractedTags.params[0].getTypeName()).toBe('({a})');
});
it('should support union type', () => {
const { extractedTags } = parseJsDoc('@param {(number|boolean)} event');
expect(extractedTags.params[0].getTypeName()).toBe('(number|boolean)');
});
it('should support array type', () => {
const { extractedTags } = parseJsDoc('@param {number[]} event');
expect(extractedTags.params[0].getTypeName()).toBe('number[]');
});
it('should support untyped array type', () => {
const { extractedTags } = parseJsDoc('@param {[]} event');
expect(extractedTags.params[0].getTypeName()).toBe('[]');
});
it('should support nullable type', () => {
const { extractedTags } = parseJsDoc('@param {?number} event');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support non nullable type', () => {
const { extractedTags } = parseJsDoc('@param {!number} event');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support optional param with []', () => {
const { extractedTags } = parseJsDoc('@param {number} [event]');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support optional param with =', () => {
const { extractedTags } = parseJsDoc('@param {number=} event');
expect(extractedTags.params[0].getTypeName()).toBe('number');
});
it('should support any type', () => {
const { extractedTags } = parseJsDoc('@param {*} event');
expect(extractedTags.params[0].getTypeName()).toBe('any');
});
});
describe('getPrettyName', () => {
it('should return @param name', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event - React event');
expect(extractedTags.params[0].getPrettyName()).toBe('event');
});
it('should fix missing space between the @param name and the description separator', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event- React event');
expect(extractedTags.params[0].getPrettyName()).toBe('event');
});
it('should fix @param name ending with . followed by a @returns tag', () => {
const { extractedTags } = parseJsDoc('@param {SyntheticEvent} event.\n');
expect(extractedTags.params[0].getPrettyName()).toBe('event');
});
});
});
describe('@returns', () => {
it('should ignore invalid @returns', () => {
const { extractedTags } = parseJsDoc('@returns');
expect(extractedTags.returns).toBeNull();
});
it('should return a @returns with a type', () => {
const { extractedTags } = parseJsDoc('@returns {string}');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
});
it('should return a @returns with a type and a description', () => {
const { extractedTags } = parseJsDoc('@returns {string} - A bar description');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
expect(extractedTags.returns.description).toBe('A bar description');
});
it('should support multiline @returns description', () => {
const { extractedTags } = parseJsDoc(
'@returns {string} - This is\na multiline\ndescription\n'
);
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
expect(extractedTags.returns.description).toBe('This is\na multiline\ndescription');
});
it('should only consider the last @returns tag when there is multiple', () => {
const { extractedTags } = parseJsDoc('@returns {string}\n@returns {number}');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('number');
});
describe('getTypeName', () => {
it('should support named type', () => {
const { extractedTags } = parseJsDoc('@returns {string}');
expect(extractedTags.returns.getTypeName()).toBe('string');
});
it('should support record type with a single field', () => {
const { extractedTags } = parseJsDoc('@returns {{a: number}}');
expect(extractedTags.returns.getTypeName()).toBe('({a: number})');
});
it('should support record type with multiple fields', () => {
const { extractedTags } = parseJsDoc('@returns {{a: number, b: string}}');
expect(extractedTags.returns.getTypeName()).toBe('({a: number, b: string})');
});
it('should support record type with a field having only a name', () => {
const { extractedTags } = parseJsDoc('@returns {{a}}');
expect(extractedTags.returns.getTypeName()).toBe('({a})');
});
it('should support array type', () => {
const { extractedTags } = parseJsDoc('@returns {integer[]}');
expect(extractedTags.returns.getTypeName()).toBe('integer[]');
});
it('should support untyped array type', () => {
const { extractedTags } = parseJsDoc('@returns {[]}');
expect(extractedTags.returns.getTypeName()).toBe('[]');
});
it('should support union type', () => {
const { extractedTags } = parseJsDoc('@returns {(number|boolean)}');
expect(extractedTags.returns.getTypeName()).toBe('(number|boolean)');
});
it('should support any type', () => {
const { extractedTags } = parseJsDoc('@returns {*}');
expect(extractedTags.returns.getTypeName()).toBe('any');
});
it('should support void', () => {
const { extractedTags } = parseJsDoc('@returns {void}');
expect(extractedTags.returns.getTypeName()).toBe('void');
});
});
});
it('should ignore unsupported JSDoc tags', () => {
const { extractedTags } = parseJsDoc('Hey!\n@param event', { tags: [] });
expect(extractedTags.params).toBeNull();
});
it('should remove extra newline characters between tags', () => {
const { extractedTags } = parseJsDoc(
'Hey!\n@param {SyntheticEvent} event - Original event.\n \n \n \n@returns {string}'
);
expect(extractedTags.params).not.toBeNull();
expect(Object.keys(extractedTags.params).length).toBe(1);
expect(extractedTags.params[0].name).toBe('event');
expect(extractedTags.params[0].type.name).toBe('SyntheticEvent');
expect(extractedTags.params[0].description).toBe('Original event.');
expect(extractedTags.returns).not.toBeNull();
expect(extractedTags.returns.type.name).toBe('string');
});
});