@nocobase/flow-engine 2.1.0-beta.42 → 2.1.0-beta.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -31
- package/lib/components/subModel/LazyDropdown.js +17 -9
- package/lib/executor/FlowExecutor.js +0 -3
- package/lib/flowContext.d.ts +6 -1
- package/lib/flowContext.js +35 -6
- package/lib/flowEngine.d.ts +4 -3
- package/lib/flowEngine.js +69 -37
- package/lib/models/flowModel.js +45 -13
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
- package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/base.js +464 -29
- package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
- package/lib/runjs-context/contexts/elementDoc.js +152 -0
- package/lib/utils/loadedPageCache.d.ts +24 -0
- package/lib/utils/loadedPageCache.js +139 -0
- package/package.json +4 -4
- package/src/__tests__/flowContext.test.ts +23 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
- package/src/__tests__/runjsContext.test.ts +18 -0
- package/src/__tests__/runjsContextImplementations.test.ts +9 -2
- package/src/__tests__/runjsLocales.test.ts +6 -5
- package/src/__tests__/viewScopedFlowEngine.test.ts +133 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +79 -37
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +150 -3
- package/src/components/subModel/LazyDropdown.tsx +16 -7
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +51 -0
- package/src/executor/FlowExecutor.ts +0 -3
- package/src/executor/__tests__/flowExecutor.test.ts +2 -4
- package/src/flowContext.ts +40 -6
- package/src/flowEngine.ts +71 -35
- package/src/models/__tests__/flowModel.test.ts +13 -28
- package/src/models/flowModel.tsx +62 -29
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
- package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/base.ts +467 -31
- package/src/runjs-context/contexts/elementDoc.ts +130 -0
- package/src/utils/loadedPageCache.ts +147 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var elementDoc_exports = {};
|
|
29
|
+
__export(elementDoc_exports, {
|
|
30
|
+
createElementPropertyDoc: () => createElementPropertyDoc,
|
|
31
|
+
createZhCNElementPropertyDoc: () => createZhCNElementPropertyDoc
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(elementDoc_exports);
|
|
34
|
+
const elementProperties = {
|
|
35
|
+
innerHTML: "Sanitized inner HTML string.",
|
|
36
|
+
outerHTML: "Sanitized outer HTML string.",
|
|
37
|
+
textContent: "Text content.",
|
|
38
|
+
style: "Inline style declaration.",
|
|
39
|
+
classList: "DOMTokenList for CSS classes.",
|
|
40
|
+
appendChild: {
|
|
41
|
+
type: "function",
|
|
42
|
+
description: "Append a DOM node or text.",
|
|
43
|
+
detail: "(child: Node | string) => void",
|
|
44
|
+
completion: { insertText: `ctx.element.appendChild('text')` }
|
|
45
|
+
},
|
|
46
|
+
setAttribute: {
|
|
47
|
+
type: "function",
|
|
48
|
+
description: "Set an HTML attribute.",
|
|
49
|
+
detail: "(name: string, value: string) => void",
|
|
50
|
+
completion: { insertText: `ctx.element.setAttribute('data-key', 'value')` }
|
|
51
|
+
},
|
|
52
|
+
getAttribute: {
|
|
53
|
+
type: "function",
|
|
54
|
+
description: "Read an HTML attribute.",
|
|
55
|
+
detail: "(name: string) => string | null",
|
|
56
|
+
completion: { insertText: `ctx.element.getAttribute('data-key')` }
|
|
57
|
+
},
|
|
58
|
+
querySelector: {
|
|
59
|
+
type: "function",
|
|
60
|
+
description: "Find the first matching descendant.",
|
|
61
|
+
detail: "(selectors: string) => Element | null",
|
|
62
|
+
completion: { insertText: `ctx.element.querySelector('.selector')` }
|
|
63
|
+
},
|
|
64
|
+
querySelectorAll: {
|
|
65
|
+
type: "function",
|
|
66
|
+
description: "Find all matching descendants.",
|
|
67
|
+
detail: "(selectors: string) => NodeListOf<Element>",
|
|
68
|
+
completion: { insertText: `ctx.element.querySelectorAll('.selector')` }
|
|
69
|
+
},
|
|
70
|
+
addEventListener: {
|
|
71
|
+
type: "function",
|
|
72
|
+
description: "Attach an event listener to the element.",
|
|
73
|
+
detail: "(type: string, listener: EventListener) => void",
|
|
74
|
+
completion: { insertText: `ctx.element.addEventListener('click', (event) => {})` }
|
|
75
|
+
},
|
|
76
|
+
removeEventListener: {
|
|
77
|
+
type: "function",
|
|
78
|
+
description: "Remove an event listener from the element.",
|
|
79
|
+
detail: "(type: string, listener: EventListener) => void",
|
|
80
|
+
completion: { insertText: `ctx.element.removeEventListener('click', handler)` }
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const zhCNElementProperties = {
|
|
84
|
+
innerHTML: "\u5DF2\u6D88\u6BD2\u7684 innerHTML \u5B57\u7B26\u4E32\u3002",
|
|
85
|
+
outerHTML: "\u5DF2\u6D88\u6BD2\u7684 outerHTML \u5B57\u7B26\u4E32\u3002",
|
|
86
|
+
textContent: "\u6587\u672C\u5185\u5BB9\u3002",
|
|
87
|
+
style: "\u5185\u8054\u6837\u5F0F\u58F0\u660E\u3002",
|
|
88
|
+
classList: "CSS class \u5217\u8868\u3002",
|
|
89
|
+
appendChild: {
|
|
90
|
+
type: "function",
|
|
91
|
+
description: "\u8FFD\u52A0 DOM \u8282\u70B9\u6216\u6587\u672C\u3002",
|
|
92
|
+
detail: "(child: Node | string) => void",
|
|
93
|
+
completion: { insertText: `ctx.element.appendChild('text')` }
|
|
94
|
+
},
|
|
95
|
+
setAttribute: {
|
|
96
|
+
type: "function",
|
|
97
|
+
description: "\u8BBE\u7F6E HTML \u5C5E\u6027\u3002",
|
|
98
|
+
detail: "(name: string, value: string) => void",
|
|
99
|
+
completion: { insertText: `ctx.element.setAttribute('data-key', 'value')` }
|
|
100
|
+
},
|
|
101
|
+
getAttribute: {
|
|
102
|
+
type: "function",
|
|
103
|
+
description: "\u8BFB\u53D6 HTML \u5C5E\u6027\u3002",
|
|
104
|
+
detail: "(name: string) => string | null",
|
|
105
|
+
completion: { insertText: `ctx.element.getAttribute('data-key')` }
|
|
106
|
+
},
|
|
107
|
+
querySelector: {
|
|
108
|
+
type: "function",
|
|
109
|
+
description: "\u67E5\u8BE2\u7B2C\u4E00\u4E2A\u5339\u914D\u7684\u540E\u4EE3\u5143\u7D20\u3002",
|
|
110
|
+
detail: "(selectors: string) => Element | null",
|
|
111
|
+
completion: { insertText: `ctx.element.querySelector('.selector')` }
|
|
112
|
+
},
|
|
113
|
+
querySelectorAll: {
|
|
114
|
+
type: "function",
|
|
115
|
+
description: "\u67E5\u8BE2\u6240\u6709\u5339\u914D\u7684\u540E\u4EE3\u5143\u7D20\u3002",
|
|
116
|
+
detail: "(selectors: string) => NodeListOf<Element>",
|
|
117
|
+
completion: { insertText: `ctx.element.querySelectorAll('.selector')` }
|
|
118
|
+
},
|
|
119
|
+
addEventListener: {
|
|
120
|
+
type: "function",
|
|
121
|
+
description: "\u7ED9\u5143\u7D20\u6DFB\u52A0\u4E8B\u4EF6\u76D1\u542C\u3002",
|
|
122
|
+
detail: "(type: string, listener: EventListener) => void",
|
|
123
|
+
completion: { insertText: `ctx.element.addEventListener('click', (event) => {})` }
|
|
124
|
+
},
|
|
125
|
+
removeEventListener: {
|
|
126
|
+
type: "function",
|
|
127
|
+
description: "\u79FB\u9664\u5143\u7D20\u4E8B\u4EF6\u76D1\u542C\u3002",
|
|
128
|
+
detail: "(type: string, listener: EventListener) => void",
|
|
129
|
+
completion: { insertText: `ctx.element.removeEventListener('click', handler)` }
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
function createElementPropertyDoc(description = "Current DOM container for this RunJS context. Usually an ElementProxy.") {
|
|
133
|
+
return {
|
|
134
|
+
description,
|
|
135
|
+
detail: "HTMLElement | ElementProxy",
|
|
136
|
+
properties: elementProperties
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
__name(createElementPropertyDoc, "createElementPropertyDoc");
|
|
140
|
+
function createZhCNElementPropertyDoc(description = "\u5F53\u524D RunJS \u4E0A\u4E0B\u6587\u7684 DOM \u5BB9\u5668\uFF0C\u901A\u5E38\u4E3A ElementProxy\u3002") {
|
|
141
|
+
return {
|
|
142
|
+
description,
|
|
143
|
+
detail: "HTMLElement | ElementProxy",
|
|
144
|
+
properties: zhCNElementProperties
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
__name(createZhCNElementPropertyDoc, "createZhCNElementPropertyDoc");
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
createElementPropertyDoc,
|
|
151
|
+
createZhCNElementPropertyDoc
|
|
152
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import type { FlowModel } from '../models';
|
|
10
|
+
type LoadedPageOptions = {
|
|
11
|
+
parentId?: string;
|
|
12
|
+
subKey?: string;
|
|
13
|
+
};
|
|
14
|
+
type DirtyKeyOptions = {
|
|
15
|
+
force?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const createLoadedPageCache: () => {
|
|
18
|
+
getDirtyKeyForModel(model?: FlowModel | null, options?: DirtyKeyOptions): string | undefined;
|
|
19
|
+
markDirty(key?: string): void;
|
|
20
|
+
shouldBypass(options?: LoadedPageOptions, isFlowSettingsEnabled?: () => boolean): boolean;
|
|
21
|
+
clear(options?: LoadedPageOptions): void;
|
|
22
|
+
mountModelToParent: <T extends FlowModel<import("..").DefaultStructure> = FlowModel<import("..").DefaultStructure>>(model: T, forceReplace?: boolean) => T;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var loadedPageCache_exports = {};
|
|
29
|
+
__export(loadedPageCache_exports, {
|
|
30
|
+
createLoadedPageCache: () => createLoadedPageCache
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(loadedPageCache_exports);
|
|
33
|
+
const getLoadedPageKey = /* @__PURE__ */ __name((options) => {
|
|
34
|
+
const parentId = options == null ? void 0 : options.parentId;
|
|
35
|
+
const subKey = options == null ? void 0 : options.subKey;
|
|
36
|
+
if (!parentId || subKey !== "page") {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
return `${parentId}::${subKey}`;
|
|
40
|
+
}, "getLoadedPageKey");
|
|
41
|
+
const getLoadedPageKeyFromModel = /* @__PURE__ */ __name((model) => {
|
|
42
|
+
var _a;
|
|
43
|
+
let current = model;
|
|
44
|
+
while (current) {
|
|
45
|
+
if (current.subKey === "page" && ((_a = current.parent) == null ? void 0 : _a.uid)) {
|
|
46
|
+
return getLoadedPageKey({ parentId: current.parent.uid, subKey: current.subKey });
|
|
47
|
+
}
|
|
48
|
+
current = current.parent;
|
|
49
|
+
}
|
|
50
|
+
return void 0;
|
|
51
|
+
}, "getLoadedPageKeyFromModel");
|
|
52
|
+
const isFlowSettingsEnabledForContext = /* @__PURE__ */ __name((context) => {
|
|
53
|
+
try {
|
|
54
|
+
return !!(context == null ? void 0 : context.flowSettingsEnabled);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}, "isFlowSettingsEnabledForContext");
|
|
59
|
+
const isFlowSettingsEnabledForModel = /* @__PURE__ */ __name((model) => {
|
|
60
|
+
if (isFlowSettingsEnabledForContext(model == null ? void 0 : model.context)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const visited = /* @__PURE__ */ new Set();
|
|
64
|
+
let engine = model == null ? void 0 : model.flowEngine;
|
|
65
|
+
while (engine && !visited.has(engine)) {
|
|
66
|
+
visited.add(engine);
|
|
67
|
+
if (isFlowSettingsEnabledForContext(engine.context)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
engine = engine.previousEngine;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}, "isFlowSettingsEnabledForModel");
|
|
74
|
+
const removeLoadedModelTree = /* @__PURE__ */ __name((model) => {
|
|
75
|
+
if (!(model == null ? void 0 : model.uid) || !model.flowEngine) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (model.flowEngine.getModel(model.uid) === model) {
|
|
79
|
+
model.flowEngine.removeModelWithSubModels(model.uid);
|
|
80
|
+
}
|
|
81
|
+
}, "removeLoadedModelTree");
|
|
82
|
+
const mountLoadedModelToParent = /* @__PURE__ */ __name((model, forceReplace = false) => {
|
|
83
|
+
var _a;
|
|
84
|
+
if (!(model == null ? void 0 : model.parent) || !model.subKey) {
|
|
85
|
+
return model;
|
|
86
|
+
}
|
|
87
|
+
const mounted = (_a = model.parent.subModels) == null ? void 0 : _a[model.subKey];
|
|
88
|
+
const existing = forceReplace && model.subType !== "array" && mounted && !Array.isArray(mounted) ? mounted : model.parent.findSubModel(model.subKey, (m) => m.uid === model.uid);
|
|
89
|
+
if (existing) {
|
|
90
|
+
if (!forceReplace || existing === model) {
|
|
91
|
+
return model;
|
|
92
|
+
}
|
|
93
|
+
removeLoadedModelTree(existing);
|
|
94
|
+
}
|
|
95
|
+
if (model.subType === "array") {
|
|
96
|
+
model.parent.addSubModel(model.subKey, model);
|
|
97
|
+
} else {
|
|
98
|
+
model.parent.setSubModel(model.subKey, model);
|
|
99
|
+
}
|
|
100
|
+
return model;
|
|
101
|
+
}, "mountLoadedModelToParent");
|
|
102
|
+
const createLoadedPageCache = /* @__PURE__ */ __name(() => {
|
|
103
|
+
const dirtyKeys = /* @__PURE__ */ new Set();
|
|
104
|
+
return {
|
|
105
|
+
getDirtyKeyForModel(model, options) {
|
|
106
|
+
if (!(options == null ? void 0 : options.force) && !isFlowSettingsEnabledForModel(model)) {
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
return getLoadedPageKeyFromModel(model);
|
|
110
|
+
},
|
|
111
|
+
markDirty(key) {
|
|
112
|
+
if (key) {
|
|
113
|
+
dirtyKeys.add(key);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
shouldBypass(options, isFlowSettingsEnabled) {
|
|
117
|
+
const key = getLoadedPageKey(options);
|
|
118
|
+
if (!key || !dirtyKeys.has(key)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return !(isFlowSettingsEnabled == null ? void 0 : isFlowSettingsEnabled());
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
clear(options) {
|
|
128
|
+
const key = getLoadedPageKey(options);
|
|
129
|
+
if (key) {
|
|
130
|
+
dirtyKeys.delete(key);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
mountModelToParent: mountLoadedModelToParent
|
|
134
|
+
};
|
|
135
|
+
}, "createLoadedPageCache");
|
|
136
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
137
|
+
0 && (module.exports = {
|
|
138
|
+
createLoadedPageCache
|
|
139
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.44",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.1.0-beta.
|
|
12
|
-
"@nocobase/shared": "2.1.0-beta.
|
|
11
|
+
"@nocobase/sdk": "2.1.0-beta.44",
|
|
12
|
+
"@nocobase/shared": "2.1.0-beta.44",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "540d4897b1696cc24c0754521b0b766978b4933c"
|
|
41
41
|
}
|
|
@@ -796,6 +796,29 @@ describe('FlowContext.getApiInfos', () => {
|
|
|
796
796
|
expect((infos.bar?.ref as any)?.url).toBe('https://example.com');
|
|
797
797
|
});
|
|
798
798
|
|
|
799
|
+
it('should include completion only when requested by getApiInfos()', async () => {
|
|
800
|
+
const ctx = new FlowContext();
|
|
801
|
+
ctx.defineMethod('bar', () => 2, {
|
|
802
|
+
description: 'Bar',
|
|
803
|
+
completion: { insertText: 'ctx.bar()' },
|
|
804
|
+
});
|
|
805
|
+
ctx.defineProperty('token', {
|
|
806
|
+
value: 't',
|
|
807
|
+
info: {
|
|
808
|
+
description: 'Token string',
|
|
809
|
+
completion: { insertText: 'ctx.token' },
|
|
810
|
+
},
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
const compact = await ctx.getApiInfos();
|
|
814
|
+
expect((compact.bar as any)?.completion).toBeUndefined();
|
|
815
|
+
expect((compact.token as any)?.completion).toBeUndefined();
|
|
816
|
+
|
|
817
|
+
const editorInfos = await ctx.getApiInfos({ includeCompletion: true });
|
|
818
|
+
expect((editorInfos.bar as any)?.completion?.insertText).toBe('ctx.bar()');
|
|
819
|
+
expect((editorInfos.token as any)?.completion?.insertText).toBe('ctx.token');
|
|
820
|
+
});
|
|
821
|
+
|
|
799
822
|
it('should return property infos with completion/ref/examples', async () => {
|
|
800
823
|
const ctx = new FlowContext();
|
|
801
824
|
ctx.defineProperty('token', {
|
|
@@ -8,9 +8,30 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { reaction } from '@nocobase/flow-engine';
|
|
11
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
12
|
import { FlowEngine } from '../flowEngine';
|
|
13
13
|
import { FlowModel } from '../models';
|
|
14
|
+
import type { IFlowModelRepository } from '../types';
|
|
15
|
+
|
|
16
|
+
class MoveRepository implements IFlowModelRepository {
|
|
17
|
+
move = vi.fn(async (_sourceId: string, _targetId: string, _position: 'before' | 'after'): Promise<void> => {});
|
|
18
|
+
|
|
19
|
+
async findOne(): Promise<Record<string, unknown> | null> {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async save(): Promise<Record<string, unknown>> {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async destroy(): Promise<boolean> {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async duplicate(): Promise<Record<string, unknown> | null> {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
14
35
|
|
|
15
36
|
describe('FlowEngine moveModel', () => {
|
|
16
37
|
let engine: FlowEngine;
|
|
@@ -20,6 +41,14 @@ describe('FlowEngine moveModel', () => {
|
|
|
20
41
|
engine.registerModels({ FlowModel });
|
|
21
42
|
});
|
|
22
43
|
|
|
44
|
+
const createParentWithChildren = () => {
|
|
45
|
+
const parent = engine.createModel({ uid: 'parent', use: 'FlowModel' });
|
|
46
|
+
parent.addSubModel('items', { uid: 'child-a', use: 'FlowModel' });
|
|
47
|
+
parent.addSubModel('items', { uid: 'child-b', use: 'FlowModel' });
|
|
48
|
+
parent.addSubModel('items', { uid: 'child-c', use: 'FlowModel' });
|
|
49
|
+
return parent;
|
|
50
|
+
};
|
|
51
|
+
|
|
23
52
|
it('keeps subModels array reactive after move so later additions trigger reactions', async () => {
|
|
24
53
|
const parent = engine.createModel({ uid: 'parent', use: 'FlowModel' });
|
|
25
54
|
parent.addSubModel('items', { uid: 'child-a', use: 'FlowModel' });
|
|
@@ -40,4 +69,55 @@ describe('FlowEngine moveModel', () => {
|
|
|
40
69
|
dispose();
|
|
41
70
|
expect(seen).toEqual([3]);
|
|
42
71
|
});
|
|
72
|
+
|
|
73
|
+
it('persists an after move when dragging forward', async () => {
|
|
74
|
+
const repository = new MoveRepository();
|
|
75
|
+
engine.setModelRepository(repository);
|
|
76
|
+
const parent = createParentWithChildren();
|
|
77
|
+
|
|
78
|
+
await engine.moveModel('child-a', 'child-c');
|
|
79
|
+
|
|
80
|
+
expect(repository.move).toHaveBeenCalledWith('child-a', 'child-c', 'after');
|
|
81
|
+
expect((parent.subModels.items as FlowModel[]).map((item) => item.uid)).toEqual(['child-b', 'child-c', 'child-a']);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('persists a before move when dragging backward', async () => {
|
|
85
|
+
const repository = new MoveRepository();
|
|
86
|
+
engine.setModelRepository(repository);
|
|
87
|
+
const parent = createParentWithChildren();
|
|
88
|
+
|
|
89
|
+
await engine.moveModel('child-c', 'child-a');
|
|
90
|
+
|
|
91
|
+
expect(repository.move).toHaveBeenCalledWith('child-c', 'child-a', 'before');
|
|
92
|
+
expect((parent.subModels.items as FlowModel[]).map((item) => item.uid)).toEqual(['child-c', 'child-a', 'child-b']);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does not persist self-drop', async () => {
|
|
96
|
+
const repository = new MoveRepository();
|
|
97
|
+
engine.setModelRepository(repository);
|
|
98
|
+
const parent = createParentWithChildren();
|
|
99
|
+
|
|
100
|
+
await engine.moveModel('child-a', 'child-a');
|
|
101
|
+
|
|
102
|
+
expect(repository.move).not.toHaveBeenCalled();
|
|
103
|
+
expect((parent.subModels.items as FlowModel[]).map((item) => item.uid)).toEqual(['child-a', 'child-b', 'child-c']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('keeps null sortIndex subModels in stable order', () => {
|
|
107
|
+
const parent = engine.createModel({
|
|
108
|
+
uid: 'parent',
|
|
109
|
+
use: 'FlowModel',
|
|
110
|
+
subModels: {
|
|
111
|
+
items: [
|
|
112
|
+
{ uid: 'child-a', use: 'FlowModel', sortIndex: null as unknown as number },
|
|
113
|
+
{ uid: 'child-b', use: 'FlowModel', sortIndex: null as unknown as number },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
parent.addSubModel('items', { uid: 'child-c', use: 'FlowModel' });
|
|
119
|
+
|
|
120
|
+
expect((parent.subModels.items as FlowModel[]).map((item) => item.uid)).toEqual(['child-a', 'child-b', 'child-c']);
|
|
121
|
+
expect((parent.subModels.items as FlowModel[]).map((item) => item.sortIndex)).toEqual([1, 2, 3]);
|
|
122
|
+
});
|
|
43
123
|
});
|
|
@@ -88,6 +88,19 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
88
88
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
89
89
|
expect(doc).toBeTruthy();
|
|
90
90
|
expect(doc?.label).toMatch(/RunJS base/);
|
|
91
|
+
expect(doc?.properties?.element).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should mark element-dependent base completions with element requirement', () => {
|
|
95
|
+
const ctx: any = { model: { constructor: { name: 'UnknownModel' } } };
|
|
96
|
+
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
97
|
+
|
|
98
|
+
expect((doc?.methods?.render as any)?.completion?.requires).toContain('element');
|
|
99
|
+
expect((doc?.properties?.viewer as any)?.properties?.popover?.completion?.requires).toContain('element');
|
|
100
|
+
expect((doc?.properties?.viewer as any)?.properties?.embed?.completion?.requires).toContain('element');
|
|
101
|
+
expect(
|
|
102
|
+
(doc?.properties?.libs as any)?.properties?.ReactDOM?.properties?.createRoot?.completion?.requires,
|
|
103
|
+
).toContain('element');
|
|
91
104
|
});
|
|
92
105
|
|
|
93
106
|
it('should support locale-specific doc', () => {
|
|
@@ -99,6 +112,11 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
99
112
|
const messageText =
|
|
100
113
|
typeof message === 'string' ? message : (message as any)?.description ?? (message as any)?.detail ?? '';
|
|
101
114
|
expect(String(messageText)).toMatch(/Ant Design 全局消息/);
|
|
115
|
+
expect((doc?.methods?.render as any)?.completion?.requires).toContain('element');
|
|
116
|
+
expect((doc?.properties?.viewer as any)?.properties?.popover?.completion?.requires).toContain('element');
|
|
117
|
+
expect(
|
|
118
|
+
(doc?.properties?.libs as any)?.properties?.ReactDOM?.properties?.createRoot?.completion?.requires,
|
|
119
|
+
).toContain('element');
|
|
102
120
|
});
|
|
103
121
|
|
|
104
122
|
it('should fallback to English when locale is not found', () => {
|
|
@@ -27,7 +27,10 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
27
27
|
const ctx: any = { model: { constructor: { name: 'JSColumnModel' } } };
|
|
28
28
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
29
29
|
expect(doc?.properties?.element).toBeTruthy();
|
|
30
|
-
|
|
30
|
+
const elementDoc: any = doc?.properties?.element;
|
|
31
|
+
expect(elementDoc?.detail).toContain('ElementProxy');
|
|
32
|
+
expect(elementDoc?.properties?.setAttribute).toBeTruthy();
|
|
33
|
+
expect(elementDoc?.properties?.querySelector).toBeTruthy();
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
it('should have record property in doc', () => {
|
|
@@ -68,7 +71,9 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
68
71
|
(ctx as any).defineProperty('api', { value: { auth: { locale: 'zh-CN' } } });
|
|
69
72
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
70
73
|
expect(doc?.label).toMatch(/JS 列/);
|
|
71
|
-
|
|
74
|
+
const elementDoc: any = doc?.properties?.element;
|
|
75
|
+
expect(elementDoc?.description).toContain('表格单元格');
|
|
76
|
+
expect(elementDoc?.properties?.addEventListener).toBeTruthy();
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
it('should create instance successfully', () => {
|
|
@@ -162,6 +167,7 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
162
167
|
const ctx: any = { model: { constructor: { name: 'JSRecordActionModel' } } };
|
|
163
168
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
164
169
|
expect(doc?.properties?.record).toBeTruthy();
|
|
170
|
+
expect(doc?.properties?.element).toBeUndefined();
|
|
165
171
|
});
|
|
166
172
|
|
|
167
173
|
it('should have filterByTk property', () => {
|
|
@@ -184,6 +190,7 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
184
190
|
const ctx: any = { model: { constructor: { name: 'JSCollectionActionModel' } } };
|
|
185
191
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
186
192
|
expect(doc?.properties?.resource).toBeTruthy();
|
|
193
|
+
expect(doc?.properties?.element).toBeUndefined();
|
|
187
194
|
});
|
|
188
195
|
|
|
189
196
|
it('should support zh-CN locale', () => {
|
|
@@ -12,6 +12,10 @@ import { getRunJSDocFor } from '..';
|
|
|
12
12
|
import { setupRunJSContexts } from '../runjs-context/setup';
|
|
13
13
|
import { FlowContext } from '../flowContext';
|
|
14
14
|
|
|
15
|
+
function getRunJSDocText(doc: unknown) {
|
|
16
|
+
return typeof doc === 'string' ? doc : (doc as any)?.description ?? (doc as any)?.detail ?? '';
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
describe('RunJS locales patch (engine doc)', () => {
|
|
16
20
|
beforeAll(async () => {
|
|
17
21
|
await setupRunJSContexts();
|
|
@@ -30,10 +34,7 @@ describe('RunJS locales patch (engine doc)', () => {
|
|
|
30
34
|
(ctx as any).defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
31
35
|
(ctx as any).defineProperty('api', { value: { auth: { locale: 'zh-CN' } } });
|
|
32
36
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
typeof message === 'string' ? message : (message as any)?.description ?? (message as any)?.detail ?? '';
|
|
36
|
-
expect(String(messageText)).toMatch(/Ant Design 全局消息 API/);
|
|
37
|
-
expect(String(doc?.methods?.t || '')).toMatch(/国际化函数/);
|
|
37
|
+
expect(String(getRunJSDocText(doc?.properties?.message))).toMatch(/Ant Design 全局消息 API/);
|
|
38
|
+
expect(String(getRunJSDocText(doc?.methods?.t))).toMatch(/国际化函数/);
|
|
38
39
|
});
|
|
39
40
|
});
|