Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utils): move checkPropTypes to utils module #2879

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/renderer-core/src/renderer/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import classnames from 'classnames';
import { create as createDataSourceEngine } from '@alilc/lowcode-datasource-engine/interpret';
import { IPublicTypeNodeSchema, IPublicTypeNodeData, IPublicTypeJSONValue, IPublicTypeCompositeValue } from '@alilc/lowcode-types';
import { isI18nData, isJSExpression, isJSFunction } from '@alilc/lowcode-utils';
import { checkPropTypes, isI18nData, isJSExpression, isJSFunction } from '@alilc/lowcode-utils';
import adapter from '../adapter';
import divFactory from '../components/Div';
import visualDomFactory from '../components/VisualDom';
Expand All @@ -21,7 +21,6 @@ import {
isFileSchema,
transformArrayToMap,
transformStringToFunction,
checkPropTypes,
getI18n,
getFileCssName,
capitalizeFirstLetter,
Expand Down
76 changes: 0 additions & 76 deletions packages/renderer-core/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@ import { isI18nData, isJSExpression } from '@alilc/lowcode-utils';
import { isEmpty } from 'lodash';
import IntlMessageFormat from 'intl-messageformat';
import pkg from '../../package.json';
import * as ReactIs from 'react-is';
import { default as ReactPropTypesSecret } from 'prop-types/lib/ReactPropTypesSecret';
import { default as factoryWithTypeCheckers } from 'prop-types/factoryWithTypeCheckers';

(window as any).sdkVersion = pkg.version;

export { pick, isEqualWith as deepEqual, cloneDeep as clone, isEmpty, throttle, debounce } from 'lodash';

const PropTypes2 = factoryWithTypeCheckers(ReactIs.isElement, true);

const EXPRESSION_TYPE = {
JSEXPRESSION: 'JSExpression',
JSFUNCTION: 'JSFunction',
Expand Down Expand Up @@ -183,77 +178,6 @@ export function transformArrayToMap(arr: any[], key: string, overwrite = true) {
return res;
}

export function isBasicType(propType: IPublicTypePropType): propType is IPublicTypeBasicType {
if (!propType) {
return false;
}
return typeof propType === 'string';
}

export function isRequiredType(propType: IPublicTypePropType): propType is IPublicTypeRequiredType {
if (!propType) {
return false;
}
return typeof propType === 'object' && propType.type && ['array', 'bool', 'func', 'number', 'object', 'string', 'node', 'element', 'any'].includes(propType.type);
}

export function transformPropTypesRuleToString(rule: IPublicTypePropType): string {
if (!rule) {
return 'PropTypes.any';
}

if (typeof rule === 'string') {
return `PropTypes.${rule}`;
}

if (isRequiredType(rule)) {
const { type, isRequired } = rule;
return `PropTypes.${type}${isRequired ? '.isRequired' : ''}`;
}

const { type, value } = rule;
switch (type) {
case 'oneOf':
return `PropTypes.oneOf([${value.map((item: any) => `"${item}"`).join(',')}])`;
case 'oneOfType':
return `PropTypes.oneOfType([${value.map((item: any) => transformPropTypesRuleToString(item)).join(', ')}])`;
case 'arrayOf':
case 'objectOf':
return `PropTypes.${type}(${transformPropTypesRuleToString(value)})`;
case 'shape':
case 'exact':
return `PropTypes.${type}({${value.map((item: any) => `${item.name}: ${transformPropTypesRuleToString(item.propType)}`).join(',')}})`;
}
}

export function checkPropTypes(value: any, name: string, rule: any, componentName: string): boolean {
let ruleFunction = rule;
if (typeof rule === 'object') {
ruleFunction = new Function(`"use strict"; const PropTypes = arguments[0]; return ${transformPropTypesRuleToString(rule)}`)(PropTypes2);
}
if (typeof rule === 'string') {
ruleFunction = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2);
}
if (!ruleFunction || typeof ruleFunction !== 'function') {
logger.warn('checkPropTypes should have a function type rule argument');
return true;
}
const err = ruleFunction(
{
[name]: value,
},
name,
componentName,
'prop',
null,
ReactPropTypesSecret,
);
if (err) {
logger.warn(err);
}
return !err;
}

/**
* transform string to a function
* @param str function in string form
Expand Down
219 changes: 0 additions & 219 deletions packages/renderer-core/tests/utils/common.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import factoryWithTypeCheckers from 'prop-types/factoryWithTypeCheckers';
import {
isSchema,
isFileSchema,
Expand All @@ -18,17 +17,9 @@ import {
parseThisRequiredExpression,
parseI18n,
parseData,
checkPropTypes,
transformPropTypesRuleToString,
isRequiredType,
isBasicType,
} from '../../src/utils/common';
import logger from '../../src/utils/logger';

var ReactIs = require('react-is');

const PropTypes = factoryWithTypeCheckers(ReactIs.isElement, true);

describe('test isSchema', () => {
it('should be false when empty value is passed', () => {
expect(isSchema(null)).toBeFalsy();
Expand Down Expand Up @@ -470,213 +461,3 @@ describe('test parseData ', () => {

});
});

describe('test isBasicType ', () => {
it('should work', () => {
expect(isBasicType(null)).toBeFalsy();
expect(isBasicType(undefined)).toBeFalsy();
expect(isBasicType({})).toBeFalsy();
expect(isBasicType({ type: 'any other type' })).toBeFalsy();
expect(isBasicType('string')).toBeTruthy();
});
});

describe('test isRequiredType', () => {
it('should work', () => {
expect(isRequiredType(null)).toBeFalsy();
expect(isRequiredType(undefined)).toBeFalsy();
expect(isRequiredType({})).toBeFalsy();
expect(isRequiredType({ type: 'any other type' })).toBeFalsy();
expect(isRequiredType('string')).toBeFalsy();
expect(isRequiredType({ type: 'string' })).toBeTruthy();
expect(isRequiredType({ type: 'string', isRequired: true })).toBeTruthy();
});
})

describe('checkPropTypes', () => {
it('should validate correctly with valid prop type', () => {
expect(checkPropTypes(123, 'age', PropTypes.number, 'TestComponent')).toBe(true);
expect(checkPropTypes('123', 'age', PropTypes.string, 'TestComponent')).toBe(true);
});

it('should log a warning and return false with invalid prop type', () => {
expect(checkPropTypes(123, 'age', PropTypes.string, 'TestComponent')).toBe(false);
expect(checkPropTypes('123', 'age', PropTypes.number, 'TestComponent')).toBe(false);
});

it('should handle custom rule functions correctly', () => {
const customRule = (props, propName) => {
if (props[propName] !== 123) {
return new Error('Invalid value');
}
};
const result = checkPropTypes(123, 'customProp', customRule, 'TestComponent');
expect(result).toBe(true);
});


it('should interpret and validate a rule given as a string', () => {
const result = checkPropTypes(123, 'age', 'PropTypes.number', 'TestComponent');
expect(result).toBe(true);
});

it('should log a warning for invalid rule type', () => {
const result = checkPropTypes(123, 'age', 123, 'TestComponent');
expect(result).toBe(true);
});

// oneOf
it('should validate correctly with valid oneOf prop type', () => {
const rule = {
type: 'oneOf',
value: ['News', 'Photos'],
}
expect(transformPropTypesRuleToString(rule)).toBe(`PropTypes.oneOf(["News","Photos"])`);
expect(checkPropTypes('News', 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes('Others', 'type', rule, 'TestComponent')).toBe(false);
});

// oneOfType
it('should validate correctly with valid oneOfType prop type', () => {
const rule = {
type: 'oneOfType',
value: ['string', 'number', {
type: 'array',
isRequired: true,
}],
};
expect(transformPropTypesRuleToString(rule)).toBe('PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array.isRequired])');
expect(checkPropTypes(['News', 'Photos'], 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes('News', 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes(123, 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes({}, 'type', rule, 'TestComponent')).toBe(false);
});

// arrayOf
it('should validate correctly with valid arrayOf prop type', () => {
const rule = {
type: 'arrayOf',
value: {
type: 'string',
isRequired: true,
},
};
expect(transformPropTypesRuleToString(rule)).toBe('PropTypes.arrayOf(PropTypes.string.isRequired)');
expect(checkPropTypes(['News', 'Photos'], 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes(['News', 123], 'type', rule, 'TestComponent')).toBe(false);
});

// objectOf
it('should validate correctly with valid objectOf prop type', () => {
const rule = {
type: 'objectOf',
value: {
type: 'string',
isRequired: true,
},
};
expect(transformPropTypesRuleToString(rule)).toBe('PropTypes.objectOf(PropTypes.string.isRequired)');
expect(checkPropTypes({ a: 'News', b: 'Photos' }, 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes({ a: 'News', b: 123 }, 'type', rule, 'TestComponent')).toBe(false);
});

// shape
it('should validate correctly with valid shape prop type', () => {
const rule = {
type: 'shape',
value: [
{
name: 'a',
propType: {
type: 'string',
isRequired: true,
},
},
{
name: 'b',
propType: {
type: 'number',
isRequired: true,
},
},
],
};
expect(transformPropTypesRuleToString(rule)).toBe('PropTypes.shape({a: PropTypes.string.isRequired,b: PropTypes.number.isRequired})');
expect(checkPropTypes({ a: 'News', b: 123 }, 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes({ a: 'News', b: 'Photos' }, 'type', rule, 'TestComponent')).toBe(false);

// isRequired
const rule2 = {
type: 'shape',
value: [
{
name: 'a',
propType: {
type: 'string',
isRequired: true,
},
},
{
name: 'b',
propType: {
type: 'number',
isRequired: false,
},
},
],
};
expect(transformPropTypesRuleToString(rule2)).toBe('PropTypes.shape({a: PropTypes.string.isRequired,b: PropTypes.number})');
expect(checkPropTypes({ a: 'News', b: 123 }, 'type', rule2, 'TestComponent')).toBe(true);
expect(checkPropTypes({ b: 123 }, 'type', rule2, 'TestComponent')).toBe(false);
});

// exact
it('should validate correctly with valid exact prop type', () => {
const rule = {
type: 'exact',
value: [
{
name: 'a',
propType: {
type: 'string',
isRequired: true,
},
},
{
name: 'b',
propType: {
type: 'number',
isRequired: true,
},
},
],
};
expect(transformPropTypesRuleToString(rule)).toBe('PropTypes.exact({a: PropTypes.string.isRequired,b: PropTypes.number.isRequired})');
expect(checkPropTypes({ a: 'News', b: 123 }, 'type', rule, 'TestComponent')).toBe(true);
expect(checkPropTypes({ a: 'News', b: 'Photos' }, 'type', rule, 'TestComponent')).toBe(false);

// isRequired
const rule2 = {
type: 'exact',
value: [
{
name: 'a',
propType: {
type: 'string',
isRequired: true,
},
},
{
name: 'b',
propType: {
type: 'number',
isRequired: false,
},
},
],
};
expect(transformPropTypesRuleToString(rule2)).toBe('PropTypes.exact({a: PropTypes.string.isRequired,b: PropTypes.number})');
expect(checkPropTypes({ a: 'News', b: 123 }, 'type', rule2, 'TestComponent')).toBe(true);
expect(checkPropTypes({ b: 123 }, 'type', rule2, 'TestComponent')).toBe(false);
});
});
1 change: 1 addition & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@alilc/lowcode-types": "1.3.1",
"lodash": "^4.17.21",
"mobx": "^6.3.0",
"prop-types": "^15.8.1",
"react": "^16"
},
"devDependencies": {
Expand Down
Loading
Loading