@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.40
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/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +613 -21
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/LazyDropdown.js +96 -39
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +9 -3
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +75 -0
- package/lib/data-source/index.js +247 -5
- package/lib/executor/FlowExecutor.js +32 -9
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +3 -0
- package/lib/flowContext.js +43 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +389 -15
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +78 -18
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.js +6 -2
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +20 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +20 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +302 -144
- package/package.json +6 -5
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +82 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +558 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +758 -19
- package/src/components/dnd/index.tsx +305 -28
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/LazyDropdown.tsx +107 -43
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +319 -36
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +7 -1
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +68 -1
- package/src/data-source/index.ts +304 -6
- package/src/executor/FlowExecutor.ts +35 -10
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +47 -3
- package/src/flowEngine.ts +445 -11
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowModel.test.ts +47 -3
- package/src/models/flowModel.tsx +119 -33
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/randomId.ts +48 -0
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +6 -2
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +25 -3
- package/src/views/useDrawer.tsx +25 -3
- package/src/views/usePage.tsx +365 -179
package/lib/views/usePage.js
CHANGED
|
@@ -54,9 +54,106 @@ var import_viewEvents = require("./viewEvents");
|
|
|
54
54
|
var import_provider = require("../provider");
|
|
55
55
|
var import_ViewScopedFlowEngine = require("../ViewScopedFlowEngine");
|
|
56
56
|
var import_variablesParams = require("../utils/variablesParams");
|
|
57
|
+
var import_runViewBeforeClose = require("./runViewBeforeClose");
|
|
57
58
|
let uuid = 0;
|
|
58
59
|
const GLOBAL_EMBED_CONTAINER_ID = "nocobase-embed-container";
|
|
59
60
|
const EMBED_REPLACING_DATA_KEY = "nocobaseEmbedReplacing";
|
|
61
|
+
function isPromiseLike(value) {
|
|
62
|
+
return !!value && typeof value.then === "function";
|
|
63
|
+
}
|
|
64
|
+
__name(isPromiseLike, "isPromiseLike");
|
|
65
|
+
function closeReplacingGlobalEmbed(target, activeView) {
|
|
66
|
+
target.dataset[EMBED_REPLACING_DATA_KEY] = "1";
|
|
67
|
+
try {
|
|
68
|
+
const closeResult = activeView.close();
|
|
69
|
+
if (isPromiseLike(closeResult)) {
|
|
70
|
+
return Promise.resolve(closeResult).finally(() => {
|
|
71
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
75
|
+
return closeResult;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
delete target.dataset[EMBED_REPLACING_DATA_KEY];
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
__name(closeReplacingGlobalEmbed, "closeReplacingGlobalEmbed");
|
|
82
|
+
function createPendingGlobalEmbedView(openedPromise, inputArgs, preventClose) {
|
|
83
|
+
let openedPage;
|
|
84
|
+
const pendingActions = {};
|
|
85
|
+
const readyPromise = openedPromise.then(({ page }) => {
|
|
86
|
+
openedPage = page;
|
|
87
|
+
if (openedPage) {
|
|
88
|
+
if ("beforeClose" in pendingActions) {
|
|
89
|
+
openedPage.beforeClose = pendingActions.beforeClose;
|
|
90
|
+
}
|
|
91
|
+
if ("update" in pendingActions) {
|
|
92
|
+
openedPage.update(pendingActions.update);
|
|
93
|
+
}
|
|
94
|
+
if ("footer" in pendingActions) {
|
|
95
|
+
openedPage.setFooter(pendingActions.footer);
|
|
96
|
+
}
|
|
97
|
+
if ("header" in pendingActions) {
|
|
98
|
+
openedPage.setHeader(pendingActions.header);
|
|
99
|
+
}
|
|
100
|
+
if (pendingActions.destroyed) {
|
|
101
|
+
openedPage.destroy(pendingActions.destroyed.result);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { page };
|
|
105
|
+
});
|
|
106
|
+
return Object.assign(
|
|
107
|
+
readyPromise.then(({ page }) => page ? page : false),
|
|
108
|
+
{
|
|
109
|
+
type: "embed",
|
|
110
|
+
inputArgs,
|
|
111
|
+
preventClose,
|
|
112
|
+
Header: null,
|
|
113
|
+
Footer: null,
|
|
114
|
+
get beforeClose() {
|
|
115
|
+
return (openedPage == null ? void 0 : openedPage.beforeClose) ?? pendingActions.beforeClose;
|
|
116
|
+
},
|
|
117
|
+
set beforeClose(value) {
|
|
118
|
+
if (openedPage) {
|
|
119
|
+
openedPage.beforeClose = value;
|
|
120
|
+
} else {
|
|
121
|
+
pendingActions.beforeClose = value;
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
close: /* @__PURE__ */ __name((result, force) => readyPromise.then(({ page }) => page ? page.close(result, force) : false), "close"),
|
|
125
|
+
destroy: /* @__PURE__ */ __name((result) => {
|
|
126
|
+
if (openedPage) {
|
|
127
|
+
openedPage.destroy(result);
|
|
128
|
+
} else {
|
|
129
|
+
pendingActions.destroyed = { result };
|
|
130
|
+
}
|
|
131
|
+
}, "destroy"),
|
|
132
|
+
update: /* @__PURE__ */ __name((newConfig) => {
|
|
133
|
+
if (openedPage) {
|
|
134
|
+
openedPage.update(newConfig);
|
|
135
|
+
} else {
|
|
136
|
+
pendingActions.update = { ...pendingActions.update, ...newConfig };
|
|
137
|
+
}
|
|
138
|
+
}, "update"),
|
|
139
|
+
setFooter: /* @__PURE__ */ __name((footer) => {
|
|
140
|
+
if (openedPage) {
|
|
141
|
+
openedPage.setFooter(footer);
|
|
142
|
+
} else {
|
|
143
|
+
pendingActions.footer = footer;
|
|
144
|
+
}
|
|
145
|
+
}, "setFooter"),
|
|
146
|
+
setHeader: /* @__PURE__ */ __name((header) => {
|
|
147
|
+
if (openedPage) {
|
|
148
|
+
openedPage.setHeader(header);
|
|
149
|
+
} else {
|
|
150
|
+
pendingActions.header = header;
|
|
151
|
+
}
|
|
152
|
+
}, "setHeader")
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
__name(createPendingGlobalEmbedView, "createPendingGlobalEmbedView");
|
|
60
157
|
const PageElementsHolder = import_react.default.memo(
|
|
61
158
|
import_react.default.forwardRef((props, ref) => {
|
|
62
159
|
const [elements, patchElement] = (0, import_usePatchElement.default)();
|
|
@@ -67,168 +164,229 @@ const PageElementsHolder = import_react.default.memo(
|
|
|
67
164
|
function usePage() {
|
|
68
165
|
const holderRef = import_react.default.useRef(null);
|
|
69
166
|
const globalEmbedActiveRef = import_react.default.useRef(null);
|
|
167
|
+
const globalEmbedReplacementTokenRef = import_react.default.useRef(0);
|
|
70
168
|
const open = /* @__PURE__ */ __name((config, flowContext) => {
|
|
71
|
-
var _a, _b, _c;
|
|
72
|
-
const parentEngine = flowContext == null ? void 0 : flowContext.engine;
|
|
73
|
-
uuid += 1;
|
|
74
|
-
const pageRef = import_react.default.createRef();
|
|
75
|
-
let closeFunc;
|
|
76
|
-
let resolvePromise;
|
|
77
|
-
const promise = new Promise((resolve) => {
|
|
78
|
-
resolvePromise = resolve;
|
|
79
|
-
});
|
|
80
|
-
const FooterComponent = /* @__PURE__ */ __name(({ children }) => {
|
|
81
|
-
import_react.default.useEffect(() => {
|
|
82
|
-
var _a2;
|
|
83
|
-
(_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(children);
|
|
84
|
-
return () => {
|
|
85
|
-
var _a3;
|
|
86
|
-
(_a3 = pageRef.current) == null ? void 0 : _a3.setFooter(null);
|
|
87
|
-
};
|
|
88
|
-
}, [children]);
|
|
89
|
-
return null;
|
|
90
|
-
}, "FooterComponent");
|
|
91
|
-
const HeaderComponent = /* @__PURE__ */ __name((props) => {
|
|
92
|
-
import_react.default.useEffect(() => {
|
|
93
|
-
var _a2;
|
|
94
|
-
(_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(props);
|
|
95
|
-
return () => {
|
|
96
|
-
var _a3;
|
|
97
|
-
(_a3 = pageRef.current) == null ? void 0 : _a3.setHeader(null);
|
|
98
|
-
};
|
|
99
|
-
}, [props]);
|
|
100
|
-
return null;
|
|
101
|
-
}, "HeaderComponent");
|
|
102
169
|
const {
|
|
103
170
|
target,
|
|
104
171
|
content,
|
|
105
172
|
preventClose,
|
|
106
173
|
inheritContext = true,
|
|
107
174
|
inputArgs: viewInputArgs = {},
|
|
175
|
+
onOpenCancelled,
|
|
108
176
|
...restConfig
|
|
109
177
|
} = config;
|
|
110
178
|
const isGlobalEmbedContainer = target instanceof HTMLElement && target.id === GLOBAL_EMBED_CONTAINER_ID;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
179
|
+
const openCurrentPage = /* @__PURE__ */ __name(() => {
|
|
180
|
+
var _a, _b, _c;
|
|
181
|
+
const parentEngine = flowContext == null ? void 0 : flowContext.engine;
|
|
182
|
+
uuid += 1;
|
|
183
|
+
const pageRef = import_react.default.createRef();
|
|
184
|
+
let closeFunc;
|
|
185
|
+
let resolvePromise;
|
|
186
|
+
const promise = new Promise((resolve) => {
|
|
187
|
+
resolvePromise = resolve;
|
|
188
|
+
});
|
|
189
|
+
const FooterComponent = /* @__PURE__ */ __name(({ children }) => {
|
|
190
|
+
import_react.default.useEffect(() => {
|
|
191
|
+
var _a2;
|
|
192
|
+
(_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(children);
|
|
193
|
+
return () => {
|
|
194
|
+
var _a3;
|
|
195
|
+
(_a3 = pageRef.current) == null ? void 0 : _a3.setFooter(null);
|
|
196
|
+
};
|
|
197
|
+
}, [children]);
|
|
198
|
+
return null;
|
|
199
|
+
}, "FooterComponent");
|
|
200
|
+
const HeaderComponent = /* @__PURE__ */ __name((props) => {
|
|
201
|
+
import_react.default.useEffect(() => {
|
|
202
|
+
var _a2;
|
|
203
|
+
(_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(props);
|
|
204
|
+
return () => {
|
|
205
|
+
var _a3;
|
|
206
|
+
(_a3 = pageRef.current) == null ? void 0 : _a3.setHeader(null);
|
|
207
|
+
};
|
|
208
|
+
}, [props]);
|
|
209
|
+
return null;
|
|
210
|
+
}, "HeaderComponent");
|
|
211
|
+
const ctx = new import_flowContext.FlowContext();
|
|
212
|
+
const scopedEngine = (0, import_ViewScopedFlowEngine.createViewScopedEngine)(flowContext.engine);
|
|
213
|
+
const openerEngine = (0, import_viewEvents.resolveOpenerEngine)(parentEngine, scopedEngine);
|
|
214
|
+
ctx.defineProperty("engine", { value: scopedEngine });
|
|
215
|
+
ctx.addDelegate(scopedEngine.context);
|
|
216
|
+
if (inheritContext) {
|
|
217
|
+
ctx.addDelegate(flowContext);
|
|
218
|
+
} else {
|
|
219
|
+
ctx.addDelegate(flowContext.engine.context);
|
|
118
220
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
(
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
221
|
+
let destroyed = false;
|
|
222
|
+
let closingPromise;
|
|
223
|
+
const currentPage = {
|
|
224
|
+
type: "embed",
|
|
225
|
+
inputArgs: viewInputArgs,
|
|
226
|
+
preventClose: !!config.preventClose,
|
|
227
|
+
beforeClose: void 0,
|
|
228
|
+
destroy: /* @__PURE__ */ __name((result) => {
|
|
229
|
+
var _a2, _b2, _c2, _d, _e;
|
|
230
|
+
if (destroyed) return;
|
|
231
|
+
destroyed = true;
|
|
232
|
+
(_a2 = config.onClose) == null ? void 0 : _a2.call(config);
|
|
233
|
+
resolvePromise == null ? void 0 : resolvePromise(result);
|
|
234
|
+
(_b2 = pageRef.current) == null ? void 0 : _b2.destroy();
|
|
235
|
+
closeFunc == null ? void 0 : closeFunc();
|
|
236
|
+
if (isGlobalEmbedContainer && globalEmbedActiveRef.current === currentPage) {
|
|
237
|
+
globalEmbedActiveRef.current = null;
|
|
238
|
+
}
|
|
239
|
+
const isReplacing = isGlobalEmbedContainer && target instanceof HTMLElement && ((_c2 = target.dataset) == null ? void 0 : _c2[EMBED_REPLACING_DATA_KEY]) === "1";
|
|
240
|
+
if (!isReplacing) {
|
|
241
|
+
const openerEmitter = openerEngine == null ? void 0 : openerEngine.emitter;
|
|
242
|
+
(0, import_viewEvents.bumpViewActivatedVersion)(openerEmitter);
|
|
243
|
+
(_e = openerEmitter == null ? void 0 : openerEmitter.emit) == null ? void 0 : _e.call(openerEmitter, import_viewEvents.VIEW_ACTIVATED_EVENT, { type: "embed", viewUid: (_d = currentPage == null ? void 0 : currentPage.inputArgs) == null ? void 0 : _d.viewUid });
|
|
244
|
+
}
|
|
245
|
+
scopedEngine.unlinkFromStack();
|
|
246
|
+
}, "destroy"),
|
|
247
|
+
update: /* @__PURE__ */ __name((newConfig) => {
|
|
248
|
+
var _a2;
|
|
249
|
+
return (_a2 = pageRef.current) == null ? void 0 : _a2.update(newConfig);
|
|
250
|
+
}, "update"),
|
|
251
|
+
close: /* @__PURE__ */ __name((result, force) => {
|
|
252
|
+
if (destroyed) {
|
|
253
|
+
return Promise.resolve(true);
|
|
254
|
+
}
|
|
255
|
+
if (closingPromise) {
|
|
256
|
+
return closingPromise;
|
|
257
|
+
}
|
|
258
|
+
closingPromise = (async () => {
|
|
259
|
+
var _a2, _b2;
|
|
260
|
+
try {
|
|
261
|
+
if (preventClose && !force) {
|
|
262
|
+
closingPromise = void 0;
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
const shouldClose = await (0, import_runViewBeforeClose.runViewBeforeClose)(currentPage, { result, force });
|
|
266
|
+
if (destroyed) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
if (!shouldClose) {
|
|
270
|
+
closingPromise = void 0;
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (config.triggerByRouter && ((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.navigation) == null ? void 0 : _b2.back)) {
|
|
274
|
+
config.inputArgs.navigation.back();
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
currentPage.destroy(result);
|
|
278
|
+
return true;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (!destroyed) {
|
|
281
|
+
closingPromise = void 0;
|
|
282
|
+
}
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
})();
|
|
286
|
+
return closingPromise;
|
|
287
|
+
}, "close"),
|
|
288
|
+
Header: HeaderComponent,
|
|
289
|
+
Footer: FooterComponent,
|
|
290
|
+
setFooter: /* @__PURE__ */ __name((footer) => {
|
|
291
|
+
var _a2;
|
|
292
|
+
(_a2 = pageRef.current) == null ? void 0 : _a2.setFooter(footer);
|
|
293
|
+
}, "setFooter"),
|
|
294
|
+
setHeader: /* @__PURE__ */ __name((header) => {
|
|
295
|
+
var _a2;
|
|
296
|
+
(_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(header);
|
|
297
|
+
}, "setHeader"),
|
|
298
|
+
navigation: (_a = config.inputArgs) == null ? void 0 : _a.navigation,
|
|
299
|
+
get record() {
|
|
300
|
+
return (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx);
|
|
159
301
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
302
|
+
};
|
|
303
|
+
ctx.defineProperty("view", {
|
|
304
|
+
get: /* @__PURE__ */ __name(() => currentPage, "get"),
|
|
305
|
+
// 仅当访问关联字段或前端无本地记录数据时,才交给服务端解析
|
|
306
|
+
resolveOnServer: (0, import_variablesParams.createViewRecordResolveOnServer)(ctx, () => (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx))
|
|
307
|
+
});
|
|
308
|
+
(0, import_createViewMeta.registerPopupVariable)(ctx, currentPage);
|
|
309
|
+
const PageWithContext = (0, import__.observer)(
|
|
310
|
+
() => {
|
|
311
|
+
var _a2, _b2, _c2, _d;
|
|
312
|
+
const mountedRef = import_react.default.useRef(false);
|
|
313
|
+
const pageContent = import_react.default.useMemo(
|
|
314
|
+
() => typeof content === "function" ? content(currentPage, ctx) : content,
|
|
315
|
+
[]
|
|
316
|
+
);
|
|
317
|
+
void ctx.themeToken;
|
|
318
|
+
import_react.default.useEffect(() => {
|
|
319
|
+
var _a3;
|
|
320
|
+
(_a3 = config.onOpen) == null ? void 0 : _a3.call(config, currentPage, ctx);
|
|
321
|
+
}, []);
|
|
322
|
+
if (((_b2 = (_a2 = config.inputArgs) == null ? void 0 : _a2.hidden) == null ? void 0 : _b2.value) && !mountedRef.current) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
mountedRef.current = true;
|
|
326
|
+
return /* @__PURE__ */ import_react.default.createElement(
|
|
327
|
+
import_PageComponent.PageComponent,
|
|
328
|
+
{
|
|
329
|
+
ref: pageRef,
|
|
330
|
+
hidden: (_d = (_c2 = config.inputArgs) == null ? void 0 : _c2.hidden) == null ? void 0 : _d.value,
|
|
331
|
+
...restConfig,
|
|
332
|
+
onClose: () => {
|
|
333
|
+
return currentPage.close(config.result);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
pageContent
|
|
337
|
+
);
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
displayName: "PageWithContext"
|
|
163
341
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
(
|
|
171
|
-
}, "setFooter"),
|
|
172
|
-
setHeader: /* @__PURE__ */ __name((header) => {
|
|
173
|
-
var _a2;
|
|
174
|
-
(_a2 = pageRef.current) == null ? void 0 : _a2.setHeader(header);
|
|
175
|
-
}, "setHeader"),
|
|
176
|
-
navigation: (_a = config.inputArgs) == null ? void 0 : _a.navigation,
|
|
177
|
-
get record() {
|
|
178
|
-
return (0, import_variablesParams.getViewRecordFromParent)(flowContext, ctx);
|
|
342
|
+
);
|
|
343
|
+
const key = (viewInputArgs == null ? void 0 : viewInputArgs.viewUid) || `page-${uuid}`;
|
|
344
|
+
const page = /* @__PURE__ */ import_react.default.createElement(import_provider.FlowEngineProvider, { key, engine: scopedEngine }, /* @__PURE__ */ import_react.default.createElement(import_FlowContextProvider.FlowViewContextProvider, { context: ctx }, /* @__PURE__ */ import_react.default.createElement(PageWithContext, null)));
|
|
345
|
+
if (target && target instanceof HTMLElement) {
|
|
346
|
+
closeFunc = (_b = holderRef.current) == null ? void 0 : _b.patchElement(import_react_dom.default.createPortal(page, target, key));
|
|
347
|
+
} else {
|
|
348
|
+
closeFunc = (_c = holderRef.current) == null ? void 0 : _c.patchElement(page);
|
|
179
349
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
hidden: (_d = (_c2 = config.inputArgs) == null ? void 0 : _c2.hidden) == null ? void 0 : _d.value,
|
|
209
|
-
...restConfig,
|
|
210
|
-
onClose: () => {
|
|
211
|
-
currentPage.close(config.result);
|
|
350
|
+
if (isGlobalEmbedContainer) {
|
|
351
|
+
globalEmbedActiveRef.current = currentPage;
|
|
352
|
+
}
|
|
353
|
+
return Object.assign(promise, currentPage);
|
|
354
|
+
}, "openCurrentPage");
|
|
355
|
+
if (isGlobalEmbedContainer && globalEmbedActiveRef.current) {
|
|
356
|
+
const replacementToken = globalEmbedReplacementTokenRef.current += 1;
|
|
357
|
+
const cancelOpen = /* @__PURE__ */ __name(() => onOpenCancelled == null ? void 0 : onOpenCancelled(), "cancelOpen");
|
|
358
|
+
let closeResult;
|
|
359
|
+
try {
|
|
360
|
+
closeResult = closeReplacingGlobalEmbed(target, globalEmbedActiveRef.current);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
cancelOpen();
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
if (isPromiseLike(closeResult)) {
|
|
366
|
+
return createPendingGlobalEmbedView(
|
|
367
|
+
Promise.resolve(closeResult).then(
|
|
368
|
+
(closed) => {
|
|
369
|
+
if (closed === false || replacementToken !== globalEmbedReplacementTokenRef.current) {
|
|
370
|
+
cancelOpen();
|
|
371
|
+
return { page: null };
|
|
372
|
+
}
|
|
373
|
+
return { page: openCurrentPage() };
|
|
374
|
+
},
|
|
375
|
+
(error) => {
|
|
376
|
+
cancelOpen();
|
|
377
|
+
throw error;
|
|
212
378
|
}
|
|
213
|
-
|
|
214
|
-
|
|
379
|
+
),
|
|
380
|
+
viewInputArgs,
|
|
381
|
+
!!config.preventClose
|
|
215
382
|
);
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
displayName: "PageWithContext"
|
|
219
383
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
closeFunc = (_b = holderRef.current) == null ? void 0 : _b.patchElement(import_react_dom.default.createPortal(page, target, key));
|
|
225
|
-
} else {
|
|
226
|
-
closeFunc = (_c = holderRef.current) == null ? void 0 : _c.patchElement(page);
|
|
227
|
-
}
|
|
228
|
-
if (isGlobalEmbedContainer) {
|
|
229
|
-
globalEmbedActiveRef.current = { destroy: currentPage.destroy };
|
|
384
|
+
if (closeResult === false) {
|
|
385
|
+
cancelOpen();
|
|
386
|
+
return createPendingGlobalEmbedView(Promise.resolve({ page: null }), viewInputArgs, !!config.preventClose);
|
|
387
|
+
}
|
|
230
388
|
}
|
|
231
|
-
return
|
|
389
|
+
return openCurrentPage();
|
|
232
390
|
}, "open");
|
|
233
391
|
const api = import_react.default.useMemo(() => ({ open }), []);
|
|
234
392
|
return [api, /* @__PURE__ */ import_react.default.createElement(PageElementsHolder, { key: "page-holder", ref: holderRef })];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
3
|
+
"version": "2.1.0-alpha.40",
|
|
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,9 +8,10 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.1.0-alpha.
|
|
12
|
-
"@nocobase/shared": "2.1.0-alpha.
|
|
11
|
+
"@nocobase/sdk": "2.1.0-alpha.40",
|
|
12
|
+
"@nocobase/shared": "2.1.0-alpha.40",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
|
+
"axios": "^1.7.0",
|
|
14
15
|
"dayjs": "^1.11.9",
|
|
15
16
|
"dompurify": "^3.0.2",
|
|
16
17
|
"lodash": "^4.x",
|
|
@@ -35,6 +36,6 @@
|
|
|
35
36
|
"workflow"
|
|
36
37
|
],
|
|
37
38
|
"author": "NocoBase Team",
|
|
38
|
-
"license": "
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"license": "Apache-2.0",
|
|
40
|
+
"gitHead": "e73f99dd0abefe847f2e50ff0fea1f41a82fd048"
|
|
40
41
|
}
|
package/src/JSRunner.ts
CHANGED
|
@@ -16,12 +16,74 @@ export interface JSRunnerOptions {
|
|
|
16
16
|
version?: string;
|
|
17
17
|
/**
|
|
18
18
|
* Enable RunJS template compatibility preprocessing for `{{ ... }}`.
|
|
19
|
-
* When enabled
|
|
19
|
+
* When enabled (or falling back to version default),
|
|
20
20
|
* the code will be rewritten to call `ctx.resolveJsonTemplate(...)` at runtime.
|
|
21
21
|
*/
|
|
22
22
|
preprocessTemplates?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Decide whether RunJS `{{ ... }}` compatibility preprocessing should run.
|
|
27
|
+
*
|
|
28
|
+
* Priority:
|
|
29
|
+
* 1. Explicit `preprocessTemplates` option always wins.
|
|
30
|
+
* 2. Otherwise, `version === 'v2'` disables preprocessing.
|
|
31
|
+
* 3. Fallback keeps v1-compatible behavior (enabled).
|
|
32
|
+
*/
|
|
33
|
+
export function shouldPreprocessRunJSTemplates(
|
|
34
|
+
options?: Pick<JSRunnerOptions, 'preprocessTemplates' | 'version'>,
|
|
35
|
+
): boolean {
|
|
36
|
+
if (typeof options?.preprocessTemplates === 'boolean') {
|
|
37
|
+
return options.preprocessTemplates;
|
|
38
|
+
}
|
|
39
|
+
return options?.version !== 'v2';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Heuristic: detect likely bare `{{ctx.xxx}}` usage in executable positions (not quoted string literals).
|
|
43
|
+
const BARE_CTX_TEMPLATE_RE = /(^|[=(:,[\s)])(\{\{\s*(ctx(?:\.|\[|\?\.)[^}]*)\s*\}\})/m;
|
|
44
|
+
|
|
45
|
+
function extractDeprecatedCtxTemplateUsage(code: string): { placeholder: string; expression: string } | null {
|
|
46
|
+
const src = String(code || '');
|
|
47
|
+
const m = src.match(BARE_CTX_TEMPLATE_RE);
|
|
48
|
+
if (!m) return null;
|
|
49
|
+
const placeholder = String(m[2] || '').trim();
|
|
50
|
+
const expression = String(m[3] || '').trim();
|
|
51
|
+
if (!placeholder || !expression) return null;
|
|
52
|
+
return { placeholder, expression };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shouldHintCtxTemplateSyntax(err: any, usage: { placeholder: string; expression: string } | null): boolean {
|
|
56
|
+
const isSyntaxError = err instanceof SyntaxError || String((err as any)?.name || '') === 'SyntaxError';
|
|
57
|
+
if (!isSyntaxError) return false;
|
|
58
|
+
if (!usage) return false;
|
|
59
|
+
const msg = String((err as any)?.message || err || '');
|
|
60
|
+
return /unexpected token/i.test(msg);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function toCtxTemplateSyntaxHintError(
|
|
64
|
+
err: any,
|
|
65
|
+
usage: {
|
|
66
|
+
placeholder: string;
|
|
67
|
+
expression: string;
|
|
68
|
+
},
|
|
69
|
+
): Error {
|
|
70
|
+
const hint = `"${usage.placeholder}" has been deprecated and cannot be used as executable RunJS syntax. Use await ctx.getVar("${usage.expression}") instead, or keep "${usage.placeholder}" as a plain string.`;
|
|
71
|
+
const out = new SyntaxError(hint);
|
|
72
|
+
try {
|
|
73
|
+
(out as any).cause = err;
|
|
74
|
+
} catch (_) {
|
|
75
|
+
// ignore
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Hint-only error: avoid leaking internal bundle line numbers from stack parsers in preview UI.
|
|
79
|
+
(out as any).__runjsHideLocation = true;
|
|
80
|
+
out.stack = `${out.name}: ${out.message}`;
|
|
81
|
+
} catch (_) {
|
|
82
|
+
// ignore
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
25
87
|
export class JSRunner {
|
|
26
88
|
private globals: Record<string, any>;
|
|
27
89
|
private timeoutMs: number;
|
|
@@ -118,11 +180,13 @@ export class JSRunner {
|
|
|
118
180
|
if (err instanceof FlowExitAllException) {
|
|
119
181
|
throw err;
|
|
120
182
|
}
|
|
121
|
-
|
|
183
|
+
const usage = extractDeprecatedCtxTemplateUsage(code);
|
|
184
|
+
const outErr = shouldHintCtxTemplateSyntax(err, usage) && usage ? toCtxTemplateSyntaxHintError(err, usage) : err;
|
|
185
|
+
console.error(outErr);
|
|
122
186
|
return {
|
|
123
187
|
success: false,
|
|
124
|
-
error:
|
|
125
|
-
timeout:
|
|
188
|
+
error: outErr,
|
|
189
|
+
timeout: (outErr as any)?.message === 'Execution timed out',
|
|
126
190
|
};
|
|
127
191
|
}
|
|
128
192
|
}
|
|
@@ -62,6 +62,10 @@ export function createViewScopedEngine(parent: FlowEngine): FlowEngine {
|
|
|
62
62
|
'_nextEngine',
|
|
63
63
|
// getModel 需要在本地执行以确保全局查找时正确遍历整个引擎栈
|
|
64
64
|
'getModel',
|
|
65
|
+
// 视图销毁回调需要在本地存储,每个视图引擎有自己的销毁逻辑
|
|
66
|
+
'_destroyView',
|
|
67
|
+
'setDestroyView',
|
|
68
|
+
'destroyView',
|
|
65
69
|
]);
|
|
66
70
|
|
|
67
71
|
const handler: ProxyHandler<FlowEngine> = {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
|
-
import { JSRunner } from '../JSRunner';
|
|
11
|
+
import { JSRunner, shouldPreprocessRunJSTemplates } from '../JSRunner';
|
|
12
12
|
import { createSafeWindow } from '../utils';
|
|
13
13
|
|
|
14
14
|
describe('JSRunner', () => {
|
|
@@ -30,6 +30,18 @@ describe('JSRunner', () => {
|
|
|
30
30
|
vi.restoreAllMocks();
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
it('shouldPreprocessRunJSTemplates: explicit option has highest priority', () => {
|
|
34
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v2', preprocessTemplates: true })).toBe(true);
|
|
35
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v1', preprocessTemplates: false })).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('shouldPreprocessRunJSTemplates: falls back to version policy', () => {
|
|
39
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v1' })).toBe(true);
|
|
40
|
+
expect(shouldPreprocessRunJSTemplates({ version: 'v2' })).toBe(false);
|
|
41
|
+
expect(shouldPreprocessRunJSTemplates({})).toBe(true);
|
|
42
|
+
expect(shouldPreprocessRunJSTemplates()).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
33
45
|
it('executes simple code and returns value', async () => {
|
|
34
46
|
const runner = new JSRunner();
|
|
35
47
|
const result = await runner.run('return 1 + 2 + 3');
|
|
@@ -152,6 +164,20 @@ describe('JSRunner', () => {
|
|
|
152
164
|
expect((result.error as Error).message).toBe('Execution timed out');
|
|
153
165
|
});
|
|
154
166
|
|
|
167
|
+
it('returns friendly hint when bare {{ctx.xxx}} appears in syntax error', async () => {
|
|
168
|
+
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
169
|
+
const runner = new JSRunner();
|
|
170
|
+
const result = await runner.run('const z = {{ctx.user.id}}');
|
|
171
|
+
expect(result.success).toBe(false);
|
|
172
|
+
expect(result.error).toBeInstanceOf(SyntaxError);
|
|
173
|
+
const msg = String((result.error as any)?.message || '');
|
|
174
|
+
expect(msg).toContain('"{{ctx.user.id}}" has been deprecated');
|
|
175
|
+
expect(msg).toContain('await ctx.getVar("ctx.user.id")');
|
|
176
|
+
expect(msg).not.toContain('(at ');
|
|
177
|
+
expect((result.error as any)?.__runjsHideLocation).toBe(true);
|
|
178
|
+
expect(spy).toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
|
|
155
181
|
it('skips execution when URL contains skipRunJs=true', async () => {
|
|
156
182
|
// 模拟预览模式下通过 URL 参数跳过代码执行
|
|
157
183
|
if (typeof window !== 'undefined' && typeof window.history?.pushState === 'function') {
|