mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-06 21:40:23 +08:00
Merge pull request #546 from smartxworks/fix/validators-bugs-2
Fix some validator bugs
This commit is contained in:
commit
95da710e0a
@ -20,12 +20,21 @@ describe('Field test', () => {
|
||||
'value',
|
||||
]);
|
||||
expect(field.refComponentInfos['list' as ComponentId].refProperties).toEqual([
|
||||
'[0]',
|
||||
'[0].text',
|
||||
'0',
|
||||
'0.text',
|
||||
]);
|
||||
expect(field.rawValue).toEqual('{{input.value}} + {{list[0].text}}');
|
||||
});
|
||||
|
||||
it('allow using question mask in expression', () => {
|
||||
const field = new FieldModel('{{api.fetch?.data }}');
|
||||
expect(field.isDynamic).toEqual(true);
|
||||
expect(field.refComponentInfos['api' as ComponentId].refProperties).toEqual([
|
||||
'fetch',
|
||||
'fetch.data',
|
||||
]);
|
||||
});
|
||||
|
||||
it('parse inline variable in expression', () => {
|
||||
const field = new FieldModel('{{ [].length }}');
|
||||
expect(field.isDynamic).toEqual(true);
|
||||
@ -71,6 +80,12 @@ describe('Field test', () => {
|
||||
expect(field.refComponentInfos).toEqual({});
|
||||
});
|
||||
|
||||
it('should not count variables declared in iife in refs', () => {
|
||||
const field = new FieldModel('{{(function() {const foo = "bar"})() }}');
|
||||
expect(field.isDynamic).toEqual(true);
|
||||
expect(field.refComponentInfos).toEqual({});
|
||||
});
|
||||
|
||||
it('get value by path', () => {
|
||||
const field = new FieldModel({ foo: [{}, { bar: { baz: 'Hello, world!' } }] });
|
||||
expect(field.getPropertyByPath('foo.1.bar.baz')?.getValue()).toEqual('Hello, world!');
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
UseDependencyInExpressionSchema,
|
||||
LocalVariableInIIFEExpressionSchema,
|
||||
DynamicStateTraitAnyTypeSchema,
|
||||
NestedObjectExpressionSchema,
|
||||
} from './mock';
|
||||
import { SchemaValidator } from '../../src/validator';
|
||||
import { registry } from '../services';
|
||||
@ -68,5 +69,11 @@ describe('Validate component', () => {
|
||||
);
|
||||
expect(_result.length).toBe(0);
|
||||
});
|
||||
it('allow use nested object value in expression', () => {
|
||||
const _result = schemaValidator.validate(
|
||||
new AppModel(NestedObjectExpressionSchema, registry)
|
||||
);
|
||||
expect(_result.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -414,3 +414,44 @@ export const DynamicStateTraitSchema: ComponentSchema[] = [
|
||||
traits: [],
|
||||
},
|
||||
];
|
||||
|
||||
export const NestedObjectExpressionSchema: ComponentSchema[] = [
|
||||
{
|
||||
id: 'api0',
|
||||
type: 'core/v1/dummy',
|
||||
properties: {},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/fetch',
|
||||
properties: {
|
||||
url: '',
|
||||
method: 'get',
|
||||
lazy: false,
|
||||
disabled: false,
|
||||
headers: {},
|
||||
body: {},
|
||||
bodyType: 'json',
|
||||
onComplete: [
|
||||
{
|
||||
componentId: '',
|
||||
method: {
|
||||
name: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
onError: [
|
||||
{
|
||||
componentId: '',
|
||||
method: {
|
||||
name: '',
|
||||
parameters: {},
|
||||
},
|
||||
// should not warn api0.fetch.code
|
||||
disabled: '{{ api0.fetch.code !== 401 }}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -20,6 +20,12 @@ import escodegen from 'escodegen';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
export type FunctionNode = ASTNode & { params: ASTNode[] };
|
||||
export type DeclaratorNode = ASTNode & { id: ASTNode };
|
||||
export type LiteralNode = ASTNode & { raw: string };
|
||||
export type ExpressionNode = ASTNode & {
|
||||
object: ExpressionNode;
|
||||
property: ExpressionNode | LiteralNode;
|
||||
};
|
||||
export class FieldModel implements IFieldModel {
|
||||
isDynamic = false;
|
||||
refComponentInfos: Record<ComponentId | ModuleId, RefInfo> = {};
|
||||
@ -161,7 +167,7 @@ export class FieldModel implements IFieldModel {
|
||||
|
||||
this.astNodes[exp] = node as ASTNode;
|
||||
|
||||
// These are varirables of iife, they should be count in refs.
|
||||
// These are variables of iife, they should be count in refs.
|
||||
let localVariables: ASTNode[] = [];
|
||||
|
||||
simpleWalk(node, {
|
||||
@ -190,18 +196,17 @@ export class FieldModel implements IFieldModel {
|
||||
|
||||
break;
|
||||
case 'MemberExpression':
|
||||
const str = exp.slice(expressionNode.start, expressionNode.end);
|
||||
let path = str.replace(lastIdentifier, '');
|
||||
if (path.startsWith('.')) {
|
||||
path = path.slice(1, path.length);
|
||||
}
|
||||
this.refComponentInfos[lastIdentifier]?.refProperties.push(path);
|
||||
this.refComponentInfos[lastIdentifier]?.refProperties.push(
|
||||
this.genPathFromMemberExpressionNode(expressionNode as ExpressionNode)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
VariableDeclarator: declarator => {
|
||||
localVariables.push((declarator as DeclaratorNode).id);
|
||||
},
|
||||
});
|
||||
|
||||
// remove localVariables from refs
|
||||
for (const key in this.refComponentInfos) {
|
||||
if (localVariables.some(({ name }) => key === name)) {
|
||||
@ -211,6 +216,21 @@ export class FieldModel implements IFieldModel {
|
||||
});
|
||||
}
|
||||
|
||||
private genPathFromMemberExpressionNode(expNode: ExpressionNode) {
|
||||
const path: string[] = [];
|
||||
function travel(node: ExpressionNode) {
|
||||
path.unshift(
|
||||
node.property?.name || (node.property as LiteralNode)?.raw || node.name
|
||||
);
|
||||
if (node.object) {
|
||||
travel(node.object);
|
||||
}
|
||||
}
|
||||
|
||||
travel(expNode);
|
||||
return path.slice(1).join('.');
|
||||
}
|
||||
|
||||
private onReferenceIdChange({ oldId, newId }: AppModelEventType['idChange']) {
|
||||
if (!this.componentModel) {
|
||||
return;
|
||||
|
@ -77,7 +77,7 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
|
||||
|
||||
private checkObjHasPath(obj: Record<string, any>, path: string) {
|
||||
const arr = path.split('.');
|
||||
const curr = obj;
|
||||
let curr = obj;
|
||||
for (const key of arr) {
|
||||
const value = curr[key];
|
||||
if (value === undefined) {
|
||||
@ -86,6 +86,7 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
|
||||
// if meet AnyTypePlaceholder, return true and skip
|
||||
return true;
|
||||
}
|
||||
curr = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -94,6 +94,7 @@ class EventHandlerValidatorRule implements TraitValidatorRule {
|
||||
traitType: trait?.type,
|
||||
traitIndex,
|
||||
});
|
||||
return results;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -93,5 +93,13 @@ describe('generateDefaultValueFromSpec function', () => {
|
||||
it('can parse any type', () => {
|
||||
expect(generateDefaultValueFromSpec({})).toEqual('');
|
||||
expect(generateDefaultValueFromSpec({}, true)).toEqual(AnyTypePlaceholder);
|
||||
expect(
|
||||
(
|
||||
generateDefaultValueFromSpec(
|
||||
Type.Object({ foo: Type.Object({ bar: Type.Any() }) }),
|
||||
true
|
||||
) as any
|
||||
).foo.bar
|
||||
).toEqual(AnyTypePlaceholder);
|
||||
});
|
||||
});
|
||||
|
@ -23,20 +23,40 @@ export function StringUnion<T extends string[]>(values: [...T], options?: any) {
|
||||
);
|
||||
}
|
||||
|
||||
function getArray(items: JSONSchema7Definition[]): JSONSchema7Type[] {
|
||||
function getArray(
|
||||
items: JSONSchema7Definition[],
|
||||
returnPlaceholderForAny = false
|
||||
): JSONSchema7Type[] {
|
||||
return items.map(item =>
|
||||
isJSONSchema(item) ? generateDefaultValueFromSpec(item) : null
|
||||
isJSONSchema(item)
|
||||
? generateDefaultValueFromSpec(item, returnPlaceholderForAny)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
function getObject(spec: JSONSchema7): JSONSchema7Object {
|
||||
function getObject(
|
||||
spec: JSONSchema7,
|
||||
returnPlaceholderForAny = false
|
||||
): JSONSchema7Object | string {
|
||||
const obj: JSONSchema7Object = {};
|
||||
|
||||
if (spec.allOf && spec.allOf.length > 0) {
|
||||
return (getArray(spec.allOf) as JSONSchema7Object[]).reduce((prev, cur) => {
|
||||
prev = Object.assign(prev, cur);
|
||||
return prev;
|
||||
}, obj);
|
||||
return (getArray(spec.allOf, returnPlaceholderForAny) as JSONSchema7Object[]).reduce(
|
||||
(prev, cur) => {
|
||||
prev = Object.assign(prev, cur);
|
||||
return prev;
|
||||
},
|
||||
obj
|
||||
);
|
||||
}
|
||||
|
||||
// if not specific property, treat it as any type
|
||||
if (!spec.properties) {
|
||||
if (returnPlaceholderForAny) {
|
||||
return AnyTypePlaceholder;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const key in spec.properties) {
|
||||
@ -44,7 +64,7 @@ function getObject(spec: JSONSchema7): JSONSchema7Object {
|
||||
if (typeof subSpec === 'boolean') {
|
||||
obj[key] = null;
|
||||
} else if (subSpec) {
|
||||
obj[key] = generateDefaultValueFromSpec(subSpec);
|
||||
obj[key] = generateDefaultValueFromSpec(subSpec, returnPlaceholderForAny);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
@ -54,11 +74,12 @@ export function generateDefaultValueFromSpec(
|
||||
spec: JSONSchema7,
|
||||
returnPlaceholderForAny = false
|
||||
): JSONSchema7Type {
|
||||
console.log(spec);
|
||||
if (!spec.type) {
|
||||
if ((spec.anyOf && spec.anyOf!.length > 0) || (spec.oneOf && spec.oneOf.length > 0)) {
|
||||
const subSpec = (spec.anyOf! || spec.oneOf)[0];
|
||||
if (typeof subSpec === 'boolean') return null;
|
||||
return generateDefaultValueFromSpec(subSpec);
|
||||
return generateDefaultValueFromSpec(subSpec, returnPlaceholderForAny);
|
||||
}
|
||||
|
||||
// It is any type
|
||||
@ -77,7 +98,7 @@ export function generateDefaultValueFromSpec(
|
||||
const subSpec = {
|
||||
type: spec.type[0],
|
||||
} as JSONSchema7;
|
||||
return generateDefaultValueFromSpec(subSpec);
|
||||
return generateDefaultValueFromSpec(subSpec, returnPlaceholderForAny);
|
||||
}
|
||||
case spec.type === 'string':
|
||||
if (spec.enum && spec.enum.length > 0) {
|
||||
@ -90,16 +111,16 @@ export function generateDefaultValueFromSpec(
|
||||
case spec.type === 'array':
|
||||
return spec.items
|
||||
? Array.isArray(spec.items)
|
||||
? getArray(spec.items)
|
||||
? getArray(spec.items, returnPlaceholderForAny)
|
||||
: isJSONSchema(spec.items)
|
||||
? [generateDefaultValueFromSpec(spec.items)]
|
||||
? [generateDefaultValueFromSpec(spec.items, returnPlaceholderForAny)]
|
||||
: null
|
||||
: [];
|
||||
case spec.type === 'number':
|
||||
case spec.type === 'integer':
|
||||
return 0;
|
||||
case spec.type === 'object':
|
||||
return getObject(spec);
|
||||
return getObject(spec, returnPlaceholderForAny);
|
||||
case spec.type === 'null':
|
||||
return null;
|
||||
default:
|
||||
|
Loading…
x
Reference in New Issue
Block a user