diff --git a/src/utils/__tests__/propTypeInfo.js b/src/utils/__tests__/propTypeInfo.js index b23fcc4b5..aad569086 100644 --- a/src/utils/__tests__/propTypeInfo.js +++ b/src/utils/__tests__/propTypeInfo.js @@ -1,4 +1,4 @@ -import { getNormalizedTypeName } from '../propTypeInfo'; +import { getNormalizedTypeName, getDefaultValue } from '../propTypeInfo'; const createPropType = (name, args, { isRequired = false } = {}) => { const propType = [{ name: 'PropTypes' }, { name }]; @@ -108,3 +108,128 @@ describe('getNormalizedTypeName', () => { expect(getNormalizedTypeName(propType)).toEqual('(string|number)[]'); }); }); + +describe('getDefaultValue', () => { + test('returns value for primitive types', () => { + const component = { + propTypes: { + message: createPropType('string'), + }, + defaultProps: { + message: 'Hello', + }, + }; + + expect(getDefaultValue(component, 'message')).toEqual('Hello'); + }); + + test('returns undefined for undefined default values', () => { + const component = { + propTypes: {}, + defaultProps: {}, + }; + + expect(getDefaultValue(component, 'name')).toBeUndefined(); + }); + + test('returns null for default values set to null', () => { + const component = { + propTypes: { + name: createPropType('string'), + }, + defaultProps: { + name: null, + }, + }; + + expect(getDefaultValue(component, 'name')).toBeNull(); + }); + + test('returns stringified boolean value when it is a boolean', () => { + const component = { + propTypes: { + disabled: createPropType('bool'), + }, + defaultProps: { + disabled: false, + }, + }; + + expect(getDefaultValue(component, 'disabled')).toEqual('false'); + }); + + test('returns a number if the default value is a number', () => { + const component = { + propTypes: { + count: createPropType('number'), + }, + defaultProps: { + count: 5, + }, + }; + + expect(getDefaultValue(component, 'count')).toEqual(5); + }); + + test('returns special number names if the default is a special number', () => { + const component = { + propTypes: { + bytes: createPropType('number'), + }, + defaultProps: { + bytes: Number.MAX_SAFE_INTEGER, + }, + }; + + expect(getDefaultValue(component, 'bytes')).toEqual( + 'Number.MAX_SAFE_INTEGER' + ); + }); + + test('returns undefined if the default value is an arbitrary object', () => { + const component = { + propTypes: { + bytes: createPropType('object'), + }, + defaultProps: { + location: { state: '1234' }, + }, + }; + + expect(getDefaultValue(component, 'location')).toBeUndefined(); + }); + + test('returns stringfied representation of array if the default value is an array', () => { + const component = { + propTypes: { + bytes: createPropType('array'), + }, + defaultProps: { + sizes: [1, 2, 3], + }, + }; + + expect(getDefaultValue(component, 'sizes')).toEqual('[1,2,3]'); + }); + + test('returns static constant if the default value is a union prop', () => { + const GAP = { + SMALL: 1, + MEDIUM: 2, + LARGE: 3, + }; + + const component = { + name: 'Grid', + propTypes: { + gap: createPropType('oneOf', [[GAP.SMALL, GAP.MEDIUM, GAP.LARGE]]), + }, + defaultProps: { + gap: GAP.SMALL, + }, + GAP, + }; + + expect(getDefaultValue(component, 'gap')).toEqual('Grid.GAP.SMALL'); + }); +}); diff --git a/src/utils/propTypeInfo.js b/src/utils/propTypeInfo.js index db8ef88ff..23ad94829 100644 --- a/src/utils/propTypeInfo.js +++ b/src/utils/propTypeInfo.js @@ -1,10 +1,30 @@ const UNION_DELIMITER = '|'; +const SPECIAL_NUMBERS = [ + 'MAX_VALUE', + 'MIN_VALUE', + 'NEGATIVE_INFINITY', + 'POSITIVE_INFINITY', + 'MAX_SAFE_INTEGER', + 'MIN_SAFE_INTEGER', + 'EPSILON', +]; + const getArgs = (propType) => propType.__reflect__.find(({ args }) => args)?.args; +const isEnum = (propType) => getRawTypeName(propType) === 'oneOf'; const isUnion = (propType) => getRawTypeName(propType) === 'oneOfType'; +const findSpecialNumber = (number) => + SPECIAL_NUMBERS.find((property) => Number[property] === number); + +const toStaticName = (name) => + name + .replace(/(.+?)(?=[A-Z])/g, '$1_') + .replace('.', '_') + .toUpperCase(); + export const getRawTypeName = (propType) => propType.__reflect__[1].name; export const getNormalizedTypeName = (propType) => { @@ -34,3 +54,40 @@ export const getNormalizedTypeName = (propType) => { return name; } }; + +export const getDefaultValue = (component, propTypeName) => { + const defaultValue = component.defaultProps?.[propTypeName]; + + if (defaultValue == null) { + return defaultValue; + } + + if (typeof defaultValue === 'boolean') { + return defaultValue.toString(); + } + + if (Array.isArray(defaultValue)) { + return JSON.stringify(defaultValue); + } + + if (typeof defaultValue === 'object') { + return undefined; + } + + if (isEnum(component.propTypes[propTypeName])) { + const staticProperty = toStaticName(propTypeName); + const property = Object.entries(component[staticProperty]).find( + ([_, value]) => value === defaultValue + )[0]; + + return `${component.name}.${staticProperty}.${property}`; + } + + if (typeof defaultValue === 'number') { + const specialNumber = findSpecialNumber(defaultValue); + + return specialNumber ? `Number.${specialNumber}` : defaultValue; + } + + return defaultValue; +};