mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 23:49:27 +08:00
feat: update ctx-func tests and parser to support async context functions
This commit is contained in:
parent
4c92a8a788
commit
319c7614c0
@ -18,10 +18,11 @@ describe('ctx function', () => {
|
|||||||
const variable = extractTemplateVariable(template);
|
const variable = extractTemplateVariable(template);
|
||||||
expect(variable).toBe(null);
|
expect(variable).toBe(null);
|
||||||
});
|
});
|
||||||
it('should handle basic context function with state', () => {
|
|
||||||
|
it('should handle basic context function with state', async () => {
|
||||||
const template = '{{$user.id}} - {{$user.name}}';
|
const template = '{{$user.id}} - {{$user.name}}';
|
||||||
const data = {
|
const data = {
|
||||||
$user({ fields, context }) {
|
async $user({ fields, context }) {
|
||||||
if (context.state.userId) {
|
if (context.state.userId) {
|
||||||
return (field) => 1;
|
return (field) => 1;
|
||||||
} else return (field) => 2;
|
} else return (field) => 2;
|
||||||
@ -30,11 +31,11 @@ describe('ctx function', () => {
|
|||||||
userId: 1,
|
userId: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toEqual('1 - 1');
|
expect(result).toEqual('1 - 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle context function without state', () => {
|
it('should handle context function without state', async () => {
|
||||||
const template = '{{$user.id}} - {{$user.name}}';
|
const template = '{{$user.id}} - {{$user.name}}';
|
||||||
const data = {
|
const data = {
|
||||||
$user({ fields, context }) {
|
$user({ fields, context }) {
|
||||||
@ -42,11 +43,11 @@ describe('ctx function', () => {
|
|||||||
},
|
},
|
||||||
state: {},
|
state: {},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toEqual('2 - 2');
|
expect(result).toEqual('2 - 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle nested context values', () => {
|
it('should handle nested context values', async () => {
|
||||||
const template = '{{$user.profile.email}} - {{$user.profile.address.city}}';
|
const template = '{{$user.profile.email}} - {{$user.profile.address.city}}';
|
||||||
const data = {
|
const data = {
|
||||||
$user({ fields, context }) {
|
$user({ fields, context }) {
|
||||||
@ -59,11 +60,11 @@ describe('ctx function', () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toEqual('test@example.com - New York');
|
expect(result).toEqual('test@example.com - New York');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle multiple context functions', () => {
|
it('should handle multiple context functions', async () => {
|
||||||
const template = '{{$user.name}} works at {{$company.name}}';
|
const template = '{{$user.name}} works at {{$company.name}}';
|
||||||
const data = {
|
const data = {
|
||||||
$user({ fields, context }) {
|
$user({ fields, context }) {
|
||||||
@ -73,22 +74,22 @@ describe('ctx function', () => {
|
|||||||
return (field) => 'NocoBase';
|
return (field) => 'NocoBase';
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toEqual('John works at NocoBase');
|
expect(result).toEqual('John works at NocoBase');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle undefined context values', () => {
|
it('should handle undefined context values', async () => {
|
||||||
const template = '{{$user.nonexistent}}';
|
const template = '{{$user.nonexistent}}';
|
||||||
const data = {
|
const data = {
|
||||||
$user({ fields, context }) {
|
$user({ fields, context }) {
|
||||||
return (field) => undefined;
|
return (field) => undefined;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle context function with array values', () => {
|
it('should handle context function with array values', async () => {
|
||||||
const template = '{{$user.roles[0]}} and {{$user.roles.1}}';
|
const template = '{{$user.roles[0]}} and {{$user.roles.1}}';
|
||||||
const data = {
|
const data = {
|
||||||
$user({ fields, context }) {
|
$user({ fields, context }) {
|
||||||
@ -99,11 +100,11 @@ describe('ctx function', () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toEqual('admin and user');
|
expect(result).toEqual('admin and user');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should escape array', () => {
|
it('should escape array', async () => {
|
||||||
const template = ' {{$user.id}} - {{$user.name}} ';
|
const template = ' {{$user.id}} - {{$user.name}} ';
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@ -116,7 +117,7 @@ describe('ctx function', () => {
|
|||||||
userId: 1,
|
userId: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const result = parser.render(template, data);
|
const result = await parser.render(template, data);
|
||||||
expect(result).toEqual(' 1 - 1 ');
|
expect(result).toEqual(' 1 - 1 ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -64,18 +64,31 @@ export class JSONTemplateParser {
|
|||||||
this._engine.registerFilter(filter.name, filter.handler);
|
this._engine.registerFilter(filter.name, filter.handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(template: string, data: any = {}): any {
|
async render(template: string, data: any = {}): Promise<any> {
|
||||||
const NamespaceMap = new Map<string, { fields: Set<String>; cachedResult: Function }>();
|
const NamespaceMap = new Map<string, { fields: Set<String>; fnWrapper: Function; fn?: any }>();
|
||||||
Object.keys(data).forEach((key) => {
|
Object.keys(data).forEach((key) => {
|
||||||
if (key.startsWith('$') || typeof data[key] === 'function') {
|
if (key.startsWith('$') || typeof data[key] === 'function') {
|
||||||
NamespaceMap.set(escape(key), { fields: new Set<String>(), cachedResult: null });
|
NamespaceMap.set(escape(key), { fields: new Set<String>(), fnWrapper: data[key] });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const parsed = this.parse(template, { NamespaceMap });
|
const parsed = this.parse(template, { NamespaceMap });
|
||||||
|
const fnPromises = Array.from(NamespaceMap.entries()).map(async ([key, { fields, fnWrapper }]) => {
|
||||||
|
const fn = await fnWrapper({ fields: Array.from(fields), context: data });
|
||||||
|
return { key, fn };
|
||||||
|
});
|
||||||
|
const fns = await Promise.all(fnPromises);
|
||||||
|
fns.forEach(({ key, fn }) => {
|
||||||
|
const NS = NamespaceMap.get(key);
|
||||||
|
NS.fn = fn;
|
||||||
|
});
|
||||||
|
|
||||||
return parsed(data);
|
return parsed(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
parse = (value: any, opts?: { NamespaceMap: Map<string, { fields: Set<String>; cachedResult: Function }> }) => {
|
parse = (
|
||||||
|
value: any,
|
||||||
|
opts?: { NamespaceMap: Map<string, { fields: Set<String>; fnWrapper: Function; fn?: Function }> },
|
||||||
|
) => {
|
||||||
const engine = this.engine;
|
const engine = this.engine;
|
||||||
function type(value) {
|
function type(value) {
|
||||||
let valueType: string = typeof value;
|
let valueType: string = typeof value;
|
||||||
@ -161,20 +174,15 @@ export class JSONTemplateParser {
|
|||||||
if (opts?.NamespaceMap && opts.NamespaceMap.has(template.variableSegments[0])) {
|
if (opts?.NamespaceMap && opts.NamespaceMap.has(template.variableSegments[0])) {
|
||||||
const scopeKey = template.variableSegments[0];
|
const scopeKey = template.variableSegments[0];
|
||||||
const NS = opts.NamespaceMap.get(scopeKey);
|
const NS = opts.NamespaceMap.get(scopeKey);
|
||||||
const cachedFn = NS.cachedResult;
|
const fn = NS.fn;
|
||||||
const field = getFieldName({
|
const field = getFieldName({
|
||||||
variableName: template.variableName,
|
variableName: template.variableName,
|
||||||
variableSegments: template.variableSegments,
|
variableSegments: template.variableSegments,
|
||||||
});
|
});
|
||||||
|
if (!fn) {
|
||||||
if (cachedFn) {
|
throw new Error(`fn not found for ${scopeKey}`);
|
||||||
return cachedFn(revertEscape(field));
|
|
||||||
} else {
|
|
||||||
const fnWrapper = get(escapedContext, scopeKey);
|
|
||||||
const fn = fnWrapper({ fields: Array.from(opts.NamespaceMap.get(scopeKey).fields), context });
|
|
||||||
NS.cachedResult = fn;
|
|
||||||
return fn(revertEscape(field));
|
|
||||||
}
|
}
|
||||||
|
return fn(revertEscape(field));
|
||||||
} else if (typeof ctxVal === 'function') {
|
} else if (typeof ctxVal === 'function') {
|
||||||
const ctxVal = get(escapedContext, template.variableName);
|
const ctxVal = get(escapedContext, template.variableName);
|
||||||
value = ctxVal();
|
value = ctxVal();
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* This file is part of the NocoBase (R) project.
|
||||||
|
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||||
|
* Authors: NocoBase Team.
|
||||||
|
*
|
||||||
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||||
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
|
*/
|
||||||
|
|
||||||
import Database, { Repository } from '@nocobase/database';
|
import Database, { Repository } from '@nocobase/database';
|
||||||
import { createMockServer, MockServer } from '@nocobase/test';
|
import { createMockServer, MockServer } from '@nocobase/test';
|
||||||
|
|
||||||
@ -15,7 +24,7 @@ describe('date filters', async () => {
|
|||||||
db = app.db;
|
db = app.db;
|
||||||
repo = db.getRepository('authenticators');
|
repo = db.getRepository('authenticators');
|
||||||
agent = app.agent();
|
agent = app.agent();
|
||||||
parse = app.jsonTemplateParser.parse;
|
parse = app.jsonTemplateParser._parse;
|
||||||
});
|
});
|
||||||
it('date filters', async () => {
|
it('date filters', async () => {
|
||||||
const template = {
|
const template = {
|
||||||
@ -24,7 +33,7 @@ describe('date filters', async () => {
|
|||||||
yesterday: '{{now | date_subtract: 1, "day" | date_format: "YYYY-MM-DD"}}',
|
yesterday: '{{now | date_subtract: 1, "day" | date_format: "YYYY-MM-DD"}}',
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsed = app.jsonTemplateParser.parse(template);
|
const parsed = app.jsonTemplateParser._parse(template);
|
||||||
const now = new Date('2025-01-01 12:00:00');
|
const now = new Date('2025-01-01 12:00:00');
|
||||||
const result = parsed({
|
const result = parsed({
|
||||||
now,
|
now,
|
||||||
@ -42,7 +51,7 @@ describe('date filters', async () => {
|
|||||||
firstOfArray1: '{{array.0}}',
|
firstOfArray1: '{{array.0}}',
|
||||||
firstOfArray2: '{{array[0]}}',
|
firstOfArray2: '{{array[0]}}',
|
||||||
};
|
};
|
||||||
const result = app.jsonTemplateParser.parse(template)({
|
const result = app.jsonTemplateParser._parse(template)({
|
||||||
user: { name: 'john' },
|
user: { name: 'john' },
|
||||||
array: ['first', 'second'],
|
array: ['first', 'second'],
|
||||||
});
|
});
|
||||||
@ -60,7 +69,7 @@ describe('date filters', async () => {
|
|||||||
form: '{{form}}',
|
form: '{{form}}',
|
||||||
$form: '{{$form}}',
|
$form: '{{$form}}',
|
||||||
};
|
};
|
||||||
const result = app.jsonTemplateParser.parse(template)({
|
const result = app.jsonTemplateParser._parse(template)({
|
||||||
form,
|
form,
|
||||||
$form: form,
|
$form: form,
|
||||||
});
|
});
|
||||||
@ -75,7 +84,7 @@ describe('date filters', async () => {
|
|||||||
key1: '{{current.key1}}',
|
key1: '{{current.key1}}',
|
||||||
key2: '{{current.key2}}',
|
key2: '{{current.key2}}',
|
||||||
};
|
};
|
||||||
const result = app.jsonTemplateParser.parse(template)({
|
const result = app.jsonTemplateParser._parse(template)({
|
||||||
current: { key1: 'value1' },
|
current: { key1: 'value1' },
|
||||||
});
|
});
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -91,7 +100,7 @@ describe('date filters', async () => {
|
|||||||
$yesterday: '{{ $now | date_subtract: 1, "day" | date_format: "YYYY-MM-DD" }}',
|
$yesterday: '{{ $now | date_subtract: 1, "day" | date_format: "YYYY-MM-DD" }}',
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsed = app.jsonTemplateParser.parse(template);
|
const parsed = app.jsonTemplateParser._parse(template);
|
||||||
const $now = new Date('2025-01-01: 12:00:00');
|
const $now = new Date('2025-01-01: 12:00:00');
|
||||||
const result = parsed({
|
const result = parsed({
|
||||||
$now,
|
$now,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user