fix: the renaming, types and tests stuffs

This commit is contained in:
MrWindlike 2022-06-14 11:09:18 +08:00
parent b6b7083e15
commit b0c88fd872
6 changed files with 74 additions and 50 deletions

View File

@ -9,52 +9,60 @@ describe('Field test', () => {
it('parse static property', () => {
const field = new FieldModel('Hello, world!');
expect(field.isDynamic).toEqual(false);
expect(field.refs).toEqual({});
expect(field.refComponentInfos).toEqual({});
expect(field.rawValue).toEqual('Hello, world!');
});
it('parse expression', () => {
const field = new FieldModel('{{input.value}} + {{list[0].text}}');
expect(field.isDynamic).toEqual(true);
expect(field.refs['input'].properties).toEqual(['value']);
expect(field.refs['list'].properties).toEqual(['[0]', '[0].text']);
expect(field.refComponentInfos['input' as ComponentId].refProperties).toEqual([
'value',
]);
expect(field.refComponentInfos['list' as ComponentId].refProperties).toEqual([
'[0]',
'[0].text',
]);
expect(field.rawValue).toEqual('{{input.value}} + {{list[0].text}}');
});
it('parse inline variable in expression', () => {
const field = new FieldModel('{{ [].length }}');
expect(field.isDynamic).toEqual(true);
expect(field.refs).toEqual({});
expect(field.refComponentInfos).toEqual({});
});
it('parse object property', () => {
const field = new FieldModel({ raw: '{{input.value}}', format: 'md' });
expect(field.isDynamic).toEqual(false);
expect(field.refs).toEqual({});
expect(field.refComponentInfos).toEqual({});
expect(field.rawValue).toEqual({ raw: '{{input.value}}', format: 'md' });
expect(field.getProperty('raw')!.rawValue).toEqual('{{input.value}}');
expect(field.getProperty('raw')!.isDynamic).toEqual(true);
expect(field.getProperty('raw')!.refs['input'].properties).toEqual(['value']);
expect(
field.getProperty('raw')!.refComponentInfos['input' as ComponentId].refProperties
).toEqual(['value']);
expect(field.getProperty('format')!.rawValue).toEqual('md');
expect(field.getProperty('format')!.isDynamic).toEqual(false);
expect(field.getProperty('format')!.refs).toEqual({});
expect(field.getProperty('format')!.refComponentInfos).toEqual({});
});
it('parse array property', () => {
const field = new FieldModel({ data: [1, '{{fetch.data}}'] });
expect(field.isDynamic).toEqual(false);
expect(field.refs).toEqual({});
expect(field.refComponentInfos).toEqual({});
expect(field.rawValue).toEqual({ data: [1, '{{fetch.data}}'] });
expect(field.getProperty('data')!.rawValue).toEqual([1, '{{fetch.data}}']);
expect(field.getProperty('data')!.isDynamic).toEqual(false);
expect(field.getProperty('data')!.refs).toEqual({});
expect(field.getProperty('data')!.refComponentInfos).toEqual({});
expect(field.getProperty('data')!.getProperty(0)!.rawValue).toEqual(1);
expect(field.getProperty('data')!.getProperty(0)!.isDynamic).toEqual(false);
expect(field.getProperty('data')!.getProperty(1)!.rawValue).toEqual('{{fetch.data}}');
expect(field.getProperty('data')!.getProperty(1)!.isDynamic).toEqual(true);
expect(field.getProperty('data')!.getProperty(1)!.refs['fetch'].properties).toEqual([
'data',
]);
expect(
field.getProperty('data')!.getProperty(1)!.refComponentInfos['fetch' as ComponentId]
.refProperties
).toEqual(['data']);
});
it('update array property', () => {
@ -85,18 +93,25 @@ describe('Field test', () => {
const text = appModel.getComponentById('text' as ComponentId);
const button = appModel.getComponentById('button' as ComponentId);
input.changeId('input1' as ComponentId);
input!.changeId('input1' as ComponentId);
expect(input.id).toEqual('input1' as ComponentId);
expect(text.properties.rawValue).toEqual({
expect(input!.id).toEqual('input1' as ComponentId);
expect(text!.properties.rawValue).toEqual({
value: {
raw: "pre {{(function () {\n const object = { value: input1.value + input1.notExistKey };\n return '-' + object.value + '-';\n}());}} end",
format: 'plain',
},
string: 'Please input here',
expressionString: "{{ 'input' }}",
array: ['input'],
expressionArray: "{{['input']}}",
object: { input: 'input' },
expressionObject: "{{{'input': 'input'}}}",
});
expect(
button.traits.find(trait => trait.type === `${CORE_VERSION}/${CoreTraitName.Event}`)
.properties.rawValue
button!.traits.find(
trait => trait.type === `${CORE_VERSION}/${CoreTraitName.Event}`
)!.properties.rawValue
).toEqual({
handlers: [
{

View File

@ -252,6 +252,12 @@ export const ChangeIdMockSchema: ComponentSchema[] = [
raw: "pre {{(function () {\n const object = { value: input.value + input.notExistKey };\n return '-' + object.value + '-';\n}());}} end",
format: 'plain',
},
string: 'Please input here',
expressionString: "{{ 'input' }}",
array: ['input'],
expressionArray: "{{['input']}}",
object: { input: 'input' },
expressionObject: "{{{'input': 'input'}}}",
},
traits: [
{

View File

@ -12,24 +12,24 @@ import {
IFieldModel,
ModuleId,
RefInfo,
ASTNode,
AppModelEventType,
} from './IAppModel';
import escodegen from 'escodegen';
import { JSONSchema7 } from 'json-schema';
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
export class FieldModel implements IFieldModel {
isDynamic = false;
refs: Record<ComponentId | ModuleId, RefInfo> = {};
private nodes: Record<string, acorn.Node> = {};
refComponentInfos: Record<ComponentId | ModuleId, RefInfo> = {};
private astNodes: Record<string, ASTNode> = {};
private value: unknown | Array<IFieldModel> | Record<string, IFieldModel>;
constructor(
value: unknown,
public spec?: JSONSchema7 & SpecOptions,
public appModel?: IAppModel,
public componentModel?: IComponentModel,
public traitModel?: ITraitModel
private appModel?: IAppModel,
private componentModel?: IComponentModel,
private traitModel?: ITraitModel
) {
this.update(value);
this.appModel?.emitter.on('idChange', this.onReferenceIdChange.bind(this));
@ -137,14 +137,14 @@ export class FieldModel implements IFieldModel {
parseExpression(this.value as string).filter(exp => typeof exp !== 'string')
);
this.refs = {};
this.nodes = {};
this.refComponentInfos = {};
this.astNodes = {};
exps.forEach(exp => {
let lastIdentifier: ComponentId = '' as ComponentId;
const node = (acornLoose as typeof acorn).parse(exp, { ecmaVersion: 2020 });
this.nodes[exp] = node;
this.astNodes[exp] = node as ASTNode;
simpleWalk(node, {
Expression: expressionNode => {
@ -155,12 +155,14 @@ export class FieldModel implements IFieldModel {
expressionNode.end
) as ComponentId;
if (this.refs[key]) {
this.refs[key].nodes.push(expressionNode as Flatten<RefInfo['nodes']>);
if (this.refComponentInfos[key]) {
this.refComponentInfos[key].componentIdASTNodes.push(
expressionNode as ASTNode
);
} else {
this.refs[key] = {
nodes: [expressionNode as Flatten<RefInfo['nodes']>],
properties: [],
this.refComponentInfos[key] = {
componentIdASTNodes: [expressionNode as ASTNode],
refProperties: [],
};
}
lastIdentifier = key;
@ -172,7 +174,7 @@ export class FieldModel implements IFieldModel {
if (path.startsWith('.')) {
path = path.slice(1, path.length);
}
this.refs[lastIdentifier]?.properties.push(path);
this.refComponentInfos[lastIdentifier]?.refProperties.push(path);
break;
default:
}
@ -181,7 +183,7 @@ export class FieldModel implements IFieldModel {
});
}
onReferenceIdChange({ oldId, newId }: { oldId: ComponentId; newId: ComponentId }) {
private onReferenceIdChange({ oldId, newId }: AppModelEventType['idChange']) {
if (!this.componentModel) {
return;
}
@ -192,20 +194,20 @@ export class FieldModel implements IFieldModel {
}
this.componentModel._isDirty = true;
this.update(newId);
} else if (this.refs[oldId]) {
} else if (this.refComponentInfos[oldId]) {
const exps = parseExpression(this.value as string);
const newExps = exps.map(exp => {
const node = this.nodes[exp.toString()];
const node = this.astNodes[exp.toString()];
if (node) {
const ref = this.refs[oldId];
const ref = this.refComponentInfos[oldId];
ref.nodes.forEach(refNode => {
ref.componentIdASTNodes.forEach(refNode => {
refNode.name = newId;
});
this.refs[newId] = ref;
delete this.refs[oldId];
this.refComponentInfos[newId] = ref;
delete this.refComponentInfos[oldId];
return [escodegen.generate(node)];
}
@ -214,6 +216,9 @@ export class FieldModel implements IFieldModel {
});
const value = expChunkToString(newExps);
if (this.traitModel) {
this.traitModel._isDirty = true;
}
this.componentModel._isDirty = true;
this.update(value);
}

View File

@ -113,7 +113,6 @@ export interface IComponentModel {
export interface ITraitModel {
// trait id only exists in model, doesn't exist in schema
appModel: IAppModel;
spec: RuntimeTrait;
id: TraitId;
parent: IComponentModel;
@ -126,15 +125,15 @@ export interface ITraitModel {
updateProperty: (key: string, value: any) => void;
}
export type ASTNode = Node & { name: string };
export type RefInfo = {
nodes: (Node & { name: string })[];
properties: string[];
componentIdASTNodes: ASTNode[];
refProperties: string[];
};
export interface IFieldModel {
// value: any;
appModel?: IAppModel;
componentModel?: IComponentModel;
spec?: JSONSchema7 & SpecOptions;
isDynamic: boolean;
rawValue: any;
@ -142,7 +141,6 @@ export interface IFieldModel {
getProperty: (key: string) => IFieldModel | void;
getValue: () => unknown | void | IFieldModel;
traverse: (cb: (f: IFieldModel, key: string) => void) => void;
onReferenceIdChange: (params: AppModelEventType['idChange']) => void;
// ids of used components in the expression
refs: Record<ComponentId | ModuleId, RefInfo>;
refComponentInfos: Record<ComponentId | ModuleId, RefInfo>;
}

View File

@ -24,7 +24,7 @@ export class TraitModel implements ITraitModel {
constructor(
trait: TraitSchema,
private registry: RegistryInterface,
public appModel: IAppModel,
private appModel: IAppModel,
public parent: IComponentModel
) {
this.schema = trait;

View File

@ -67,9 +67,9 @@ class ExpressionValidatorRule implements PropertiesValidatorRule {
// validate expression
properties.traverse((fieldModel, key) => {
Object.keys(fieldModel.refs).forEach((id: string) => {
Object.keys(fieldModel.refComponentInfos).forEach((id: string) => {
const targetComponent = appModel.getComponentById(id as ComponentId);
const paths = fieldModel.refs[id as ComponentId].properties;
const paths = fieldModel.refComponentInfos[id as ComponentId].refProperties;
if (targetComponent) {
// case 1: id is a component