@weapp-vite/miniprogram-automator 1.0.2 → 1.0.4
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/dist/index.d.mts +27 -6
- package/dist/index.mjs +133 -19
- package/dist/{launch-Bd3TZy1I.mjs → launch-Didv0lMX.mjs} +229 -225
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -202,6 +202,12 @@ interface IScreenshotOptions {
|
|
|
202
202
|
interface IAuditsOptions {
|
|
203
203
|
path?: string;
|
|
204
204
|
}
|
|
205
|
+
interface IToolCompileOptions {
|
|
206
|
+
force?: boolean;
|
|
207
|
+
}
|
|
208
|
+
interface IToolClearCacheOptions {
|
|
209
|
+
clean: 'all' | 'auth' | 'compile' | 'file' | 'network' | 'session' | 'storage';
|
|
210
|
+
}
|
|
205
211
|
type AutomatorCallable = (...args: any[]) => any;
|
|
206
212
|
/** MiniProgram 的实现。 */
|
|
207
213
|
declare class MiniProgram extends EventEmitter {
|
|
@@ -211,11 +217,11 @@ declare class MiniProgram extends EventEmitter {
|
|
|
211
217
|
private nativeIns?;
|
|
212
218
|
constructor(connection: Connection);
|
|
213
219
|
pageStack(): Promise<any>;
|
|
214
|
-
navigateTo(url: string): Promise<
|
|
215
|
-
redirectTo(url: string): Promise<
|
|
216
|
-
navigateBack(): Promise<
|
|
217
|
-
reLaunch(url: string): Promise<
|
|
218
|
-
switchTab(url: string): Promise<
|
|
220
|
+
navigateTo(url: string): Promise<any>;
|
|
221
|
+
redirectTo(url: string): Promise<any>;
|
|
222
|
+
navigateBack(): Promise<any>;
|
|
223
|
+
reLaunch(url: string): Promise<any>;
|
|
224
|
+
switchTab(url: string): Promise<any>;
|
|
219
225
|
currentPage(): Promise<Page>;
|
|
220
226
|
systemInfo(): Promise<any>;
|
|
221
227
|
callWxMethod(method: string, ...args: any[]): Promise<any>;
|
|
@@ -234,6 +240,22 @@ declare class MiniProgram extends EventEmitter {
|
|
|
234
240
|
checkVersion(): Promise<void>;
|
|
235
241
|
screenshot(options?: IScreenshotOptions): Promise<any>;
|
|
236
242
|
testAccounts(): Promise<any>;
|
|
243
|
+
/**
|
|
244
|
+
* @description 获取开发者工具 Tool 域基础信息。
|
|
245
|
+
*/
|
|
246
|
+
toolInfo(): Promise<any>;
|
|
247
|
+
/**
|
|
248
|
+
* @description 触发开发者工具执行一次项目编译。
|
|
249
|
+
*/
|
|
250
|
+
compile(options?: IToolCompileOptions): Promise<any>;
|
|
251
|
+
/**
|
|
252
|
+
* @description 清理开发者工具缓存。
|
|
253
|
+
*/
|
|
254
|
+
clearCache(options: IToolClearCacheOptions): Promise<any>;
|
|
255
|
+
/**
|
|
256
|
+
* @description 调用开发者工具 Tool 域协议方法。
|
|
257
|
+
*/
|
|
258
|
+
tool(method: string, params?: Record<string, any>): Promise<any>;
|
|
237
259
|
stopAudits(options?: IAuditsOptions): Promise<any>;
|
|
238
260
|
getTicket(): Promise<any>;
|
|
239
261
|
setTicket(ticket: string): Promise<void>;
|
|
@@ -247,7 +269,6 @@ declare class MiniProgram extends EventEmitter {
|
|
|
247
269
|
}
|
|
248
270
|
//#endregion
|
|
249
271
|
//#region src/Launcher.d.ts
|
|
250
|
-
/** IConnectOptions 的类型定义。 */
|
|
251
272
|
interface IConnectOptions {
|
|
252
273
|
wsEndpoint: string;
|
|
253
274
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -577,11 +577,11 @@ var Connection = class Connection extends EventEmitter {
|
|
|
577
577
|
//#endregion
|
|
578
578
|
//#region src/headless.ts
|
|
579
579
|
async function launchHeadlessAutomator(options) {
|
|
580
|
-
return await (await import("./launch-
|
|
580
|
+
return await (await import("./launch-Didv0lMX.mjs")).launch({ projectPath: options.projectPath });
|
|
581
581
|
}
|
|
582
582
|
//#endregion
|
|
583
583
|
//#region package.json
|
|
584
|
-
var version = "1.0.
|
|
584
|
+
var version = "1.0.4";
|
|
585
585
|
//#endregion
|
|
586
586
|
//#region src/Native.ts
|
|
587
587
|
/** Native 的实现。 */
|
|
@@ -770,6 +770,11 @@ function extractPluginId(p) {
|
|
|
770
770
|
//#endregion
|
|
771
771
|
//#region src/MiniProgram.ts
|
|
772
772
|
const CLOSE_STEP_TIMEOUT = 2e3;
|
|
773
|
+
const CURRENT_PAGE_RETRIES = 3;
|
|
774
|
+
const CURRENT_PAGE_RETRY_DELAY = 400;
|
|
775
|
+
const CHANGE_ROUTE_READY_TIMEOUT = 15e3;
|
|
776
|
+
const CHANGE_ROUTE_POLL_DELAY = 500;
|
|
777
|
+
const CHANGE_ROUTE_DEBUG_ENABLED = process.env.WEAPP_VITE_E2E_CHANGE_ROUTE_DEBUG === "1";
|
|
773
778
|
function sleep(ms) {
|
|
774
779
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
775
780
|
}
|
|
@@ -792,6 +797,16 @@ function isFnStr(value) {
|
|
|
792
797
|
const trimmed = trim(value);
|
|
793
798
|
return startWith(trimmed, "function") || startWith(trimmed, "() =>");
|
|
794
799
|
}
|
|
800
|
+
function isCurrentPageProtocolTimeout(error) {
|
|
801
|
+
return error instanceof Error && "code" in error && error.code === "DEVTOOLS_PROTOCOL_TIMEOUT" && "method" in error && error.method === "App.getCurrentPage";
|
|
802
|
+
}
|
|
803
|
+
function normalizeRoutePath(value) {
|
|
804
|
+
return String(value ?? "").split("?", 1)[0].replace(/^\/+/, "").replace(/\/+$/, "");
|
|
805
|
+
}
|
|
806
|
+
function logChangeRouteDebug(message) {
|
|
807
|
+
if (!CHANGE_ROUTE_DEBUG_ENABLED) return;
|
|
808
|
+
process.stdout.write(`[automator:changeRoute] ${message}\n`);
|
|
809
|
+
}
|
|
795
810
|
/** MiniProgram 的实现。 */
|
|
796
811
|
var MiniProgram = class extends EventEmitter {
|
|
797
812
|
appBindings = /* @__PURE__ */ new Map();
|
|
@@ -830,12 +845,20 @@ var MiniProgram = class extends EventEmitter {
|
|
|
830
845
|
return await this.changeRoute("switchTab", url);
|
|
831
846
|
}
|
|
832
847
|
async currentPage() {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
848
|
+
let lastError;
|
|
849
|
+
for (let attempt = 1; attempt <= CURRENT_PAGE_RETRIES; attempt += 1) try {
|
|
850
|
+
const { pageId, path, query } = await this.send("App.getCurrentPage");
|
|
851
|
+
return Page.create(this.connection, {
|
|
852
|
+
id: pageId,
|
|
853
|
+
path,
|
|
854
|
+
query
|
|
855
|
+
}, this.pageMap);
|
|
856
|
+
} catch (error) {
|
|
857
|
+
lastError = error;
|
|
858
|
+
if (!isCurrentPageProtocolTimeout(error) || attempt >= CURRENT_PAGE_RETRIES) throw error;
|
|
859
|
+
await sleep(CURRENT_PAGE_RETRY_DELAY);
|
|
860
|
+
}
|
|
861
|
+
throw lastError;
|
|
839
862
|
}
|
|
840
863
|
async systemInfo() {
|
|
841
864
|
return await this.callWxMethod("getSystemInfoSync");
|
|
@@ -951,6 +974,30 @@ var MiniProgram = class extends EventEmitter {
|
|
|
951
974
|
async testAccounts() {
|
|
952
975
|
return (await this.send("Tool.getTestAccounts")).accounts;
|
|
953
976
|
}
|
|
977
|
+
/**
|
|
978
|
+
* @description 获取开发者工具 Tool 域基础信息。
|
|
979
|
+
*/
|
|
980
|
+
async toolInfo() {
|
|
981
|
+
return await this.send("Tool.getInfo");
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* @description 触发开发者工具执行一次项目编译。
|
|
985
|
+
*/
|
|
986
|
+
async compile(options = {}) {
|
|
987
|
+
return await this.send("Tool.compile", options);
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* @description 清理开发者工具缓存。
|
|
991
|
+
*/
|
|
992
|
+
async clearCache(options) {
|
|
993
|
+
return await this.send("Tool.clearCache", options);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* @description 调用开发者工具 Tool 域协议方法。
|
|
997
|
+
*/
|
|
998
|
+
async tool(method, params = {}) {
|
|
999
|
+
return await this.send(`Tool.${method}`, params);
|
|
1000
|
+
}
|
|
954
1001
|
async stopAudits(options = {}) {
|
|
955
1002
|
const result = await this.send("Tool.stopAudits");
|
|
956
1003
|
if (options.path) await fs.writeFile(options.path, result.report, "utf8");
|
|
@@ -971,12 +1018,50 @@ var MiniProgram = class extends EventEmitter {
|
|
|
971
1018
|
}
|
|
972
1019
|
async changeRoute(method, url) {
|
|
973
1020
|
const currentPage = await this.currentPage();
|
|
1021
|
+
logChangeRouteDebug(`start method=${method} url=${url ?? "<none>"} current=${currentPage?.path ?? "<none>"}`);
|
|
974
1022
|
if (currentPage && isPluginPath(currentPage.path)) {
|
|
975
1023
|
const pluginId = extractPluginId(currentPage.path);
|
|
976
1024
|
await this.callPluginWxMethod(pluginId, method, { url });
|
|
977
1025
|
} else await this.callWxMethod(method, { url });
|
|
978
|
-
|
|
979
|
-
|
|
1026
|
+
const expectedRoute = normalizeRoutePath(url);
|
|
1027
|
+
const startedAt = Date.now();
|
|
1028
|
+
let lastPage;
|
|
1029
|
+
let lastError;
|
|
1030
|
+
while (Date.now() - startedAt <= CHANGE_ROUTE_READY_TIMEOUT) {
|
|
1031
|
+
try {
|
|
1032
|
+
const page = await this.currentPage();
|
|
1033
|
+
lastPage = page;
|
|
1034
|
+
logChangeRouteDebug(`poll method=${method} url=${url ?? "<none>"} current=${page?.path ?? "<none>"}`);
|
|
1035
|
+
if (!expectedRoute || normalizeRoutePath(page?.path) === expectedRoute) {
|
|
1036
|
+
logChangeRouteDebug(`ready method=${method} url=${url ?? "<none>"} current=${page?.path ?? "<none>"}`);
|
|
1037
|
+
return page;
|
|
1038
|
+
}
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
lastError = error;
|
|
1041
|
+
logChangeRouteDebug(`poll-error method=${method} url=${url ?? "<none>"} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1042
|
+
}
|
|
1043
|
+
try {
|
|
1044
|
+
const stackTop = (await this.pageStack()).at(-1);
|
|
1045
|
+
if (stackTop) {
|
|
1046
|
+
lastPage = stackTop;
|
|
1047
|
+
logChangeRouteDebug(`stack method=${method} url=${url ?? "<none>"} current=${stackTop.path}`);
|
|
1048
|
+
if (!expectedRoute || normalizeRoutePath(stackTop.path) === expectedRoute) {
|
|
1049
|
+
logChangeRouteDebug(`stack-ready method=${method} url=${url ?? "<none>"} current=${stackTop.path}`);
|
|
1050
|
+
return stackTop;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
lastError = error;
|
|
1055
|
+
logChangeRouteDebug(`stack-error method=${method} url=${url ?? "<none>"} error=${error instanceof Error ? error.message : String(error)}`);
|
|
1056
|
+
}
|
|
1057
|
+
await sleep(CHANGE_ROUTE_POLL_DELAY);
|
|
1058
|
+
}
|
|
1059
|
+
if (lastPage) {
|
|
1060
|
+
logChangeRouteDebug(`timeout method=${method} url=${url ?? "<none>"} current=${lastPage.path}`);
|
|
1061
|
+
throw new Error(`Timed out waiting route ${expectedRoute || "<current>"} after ${method}; current page: ${lastPage.path}`);
|
|
1062
|
+
}
|
|
1063
|
+
logChangeRouteDebug(`timeout method=${method} url=${url ?? "<none>"} current=<none> error=${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
1064
|
+
throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`Timed out waiting route ${expectedRoute || "<current>"} after ${method}`);
|
|
980
1065
|
}
|
|
981
1066
|
async send(method, params = {}) {
|
|
982
1067
|
return await this.connection.send(method, params);
|
|
@@ -1005,6 +1090,7 @@ const DEFAULT_TIMEOUT = 3e4;
|
|
|
1005
1090
|
const DEFAULT_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_AUTOMATOR_RUNTIME_PROVIDER";
|
|
1006
1091
|
const LEGACY_RUNTIME_PROVIDER_ENV = "WEAPP_VITE_E2E_RUNTIME_PROVIDER";
|
|
1007
1092
|
const EXTENSION_CONTEXT_INVALIDATED_RE = /Extension context invalidated/i;
|
|
1093
|
+
const WINDOWS_BATCH_CLI_RE = /\.(?:bat|cmd)$/i;
|
|
1008
1094
|
let localhostListenPatched = false;
|
|
1009
1095
|
function isExtensionContextInvalidatedError(error) {
|
|
1010
1096
|
return error instanceof Error && EXTENSION_CONTEXT_INVALIDATED_RE.test(error.message);
|
|
@@ -1026,6 +1112,25 @@ function patchNetListenToLoopback() {
|
|
|
1026
1112
|
return rawListen.apply(this, args);
|
|
1027
1113
|
};
|
|
1028
1114
|
}
|
|
1115
|
+
/** IConnectOptions 的类型定义。 */
|
|
1116
|
+
function shouldUseWindowsCommandShell(cliPath) {
|
|
1117
|
+
return isWindows && WINDOWS_BATCH_CLI_RE.test(cliPath);
|
|
1118
|
+
}
|
|
1119
|
+
function escapeWindowsCmdArg(arg) {
|
|
1120
|
+
const escaped = arg.replace(/"/g, "\"\"").replace(/%/g, "%%");
|
|
1121
|
+
return /[\s"&<>^|()]/.test(arg) ? `"${escaped}"` : escaped;
|
|
1122
|
+
}
|
|
1123
|
+
function resolveWindowsBatchSpawn(cliPath, args) {
|
|
1124
|
+
return {
|
|
1125
|
+
file: process.env.ComSpec || "cmd.exe",
|
|
1126
|
+
args: [
|
|
1127
|
+
"/d",
|
|
1128
|
+
"/s",
|
|
1129
|
+
"/c",
|
|
1130
|
+
`"${[cliPath, ...args].map(escapeWindowsCmdArg).join(" ")}"`
|
|
1131
|
+
]
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1029
1134
|
function resolveRuntimeProvider(options) {
|
|
1030
1135
|
return options.runtimeProvider || process.env[DEFAULT_RUNTIME_PROVIDER_ENV] || process.env[LEGACY_RUNTIME_PROVIDER_ENV] || "devtools";
|
|
1031
1136
|
}
|
|
@@ -1045,7 +1150,8 @@ var Launcher = class {
|
|
|
1045
1150
|
if (!await import("node:fs/promises").then((fs) => fs.access(projectPath).then(() => true).catch(() => false))) throw new Error(`Project path ${projectPath} doesn't exist`);
|
|
1046
1151
|
if (!isEmpty(projectConfig)) await this.extendProjectConfig(projectConfig, projectPath);
|
|
1047
1152
|
let processError = null;
|
|
1048
|
-
let
|
|
1153
|
+
let processExitCode = null;
|
|
1154
|
+
let processSignal = null;
|
|
1049
1155
|
args = [
|
|
1050
1156
|
...args,
|
|
1051
1157
|
"auto",
|
|
@@ -1058,17 +1164,25 @@ var Launcher = class {
|
|
|
1058
1164
|
else if (ticket) args.push("--ticket", ticket);
|
|
1059
1165
|
if (trustProject) args.push("--trust-project");
|
|
1060
1166
|
try {
|
|
1061
|
-
const
|
|
1167
|
+
const spawnTarget = shouldUseWindowsCommandShell(cliPath) ? resolveWindowsBatchSpawn(cliPath, args) : {
|
|
1168
|
+
file: cliPath,
|
|
1169
|
+
args
|
|
1170
|
+
};
|
|
1171
|
+
const child = spawn(spawnTarget.file, spawnTarget.args, {
|
|
1062
1172
|
stdio: "ignore",
|
|
1063
|
-
cwd: cwd || void 0
|
|
1173
|
+
cwd: cwd || void 0,
|
|
1174
|
+
...shouldUseWindowsCommandShell(cliPath) ? {
|
|
1175
|
+
windowsHide: true,
|
|
1176
|
+
windowsVerbatimArguments: true
|
|
1177
|
+
} : {}
|
|
1064
1178
|
});
|
|
1065
1179
|
child.on("error", (error) => {
|
|
1066
1180
|
processError = error;
|
|
1067
1181
|
});
|
|
1068
|
-
child.on("exit", () => {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
}
|
|
1182
|
+
child.on("exit", (code, signal) => {
|
|
1183
|
+
processExitCode = code;
|
|
1184
|
+
processSignal = signal;
|
|
1185
|
+
if (code !== 0 || signal) processError = /* @__PURE__ */ new Error(`DevTools cli exited unexpectedly with code ${code ?? "null"}${signal ? ` and signal ${signal}` : ""}`);
|
|
1072
1186
|
});
|
|
1073
1187
|
child.unref();
|
|
1074
1188
|
} catch (error) {
|
|
@@ -1078,7 +1192,7 @@ var Launcher = class {
|
|
|
1078
1192
|
let lastConnectError = null;
|
|
1079
1193
|
await waitUntil(async () => {
|
|
1080
1194
|
try {
|
|
1081
|
-
if (processError
|
|
1195
|
+
if (processError) return true;
|
|
1082
1196
|
const candidate = await this.connectTool({ wsEndpoint: `ws://127.0.0.1:${port}` });
|
|
1083
1197
|
try {
|
|
1084
1198
|
await candidate.checkVersion();
|
|
@@ -1098,7 +1212,7 @@ var Launcher = class {
|
|
|
1098
1212
|
if (!miniProgram) {
|
|
1099
1213
|
if (processError) throw new Error("Failed to launch wechat web devTools, please make sure cliPath is correctly specified");
|
|
1100
1214
|
if (lastConnectError) throw lastConnectError;
|
|
1101
|
-
if (
|
|
1215
|
+
if (processExitCode !== null || processSignal) throw new Error("Failed to launch wechat web devTools, please make sure http port is open");
|
|
1102
1216
|
throw new Error("Failed connecting to devtools websocket endpoint");
|
|
1103
1217
|
}
|
|
1104
1218
|
const resolvedMiniProgram = miniProgram;
|
|
@@ -27,9 +27,12 @@ function createAppInstance(definition) {
|
|
|
27
27
|
return instance;
|
|
28
28
|
}
|
|
29
29
|
//#endregion
|
|
30
|
-
//#region ../../mpcore/packages/simulator/src/runtime/componentInstance.ts
|
|
30
|
+
//#region ../../mpcore/packages/simulator/src/runtime/componentInstance/shared.ts
|
|
31
31
|
const ARRAY_INDEX_PATH_RE$1 = /\[(\d+)\]/g;
|
|
32
32
|
const ARRAY_INDEX_SEGMENT_RE$1 = /^\d+$/;
|
|
33
|
+
function cloneObject$1(value) {
|
|
34
|
+
return JSON.parse(JSON.stringify(value));
|
|
35
|
+
}
|
|
33
36
|
function bindFunction$1(target, key, value) {
|
|
34
37
|
if (typeof value !== "function") {
|
|
35
38
|
target[key] = value;
|
|
@@ -37,14 +40,14 @@ function bindFunction$1(target, key, value) {
|
|
|
37
40
|
}
|
|
38
41
|
target[key] = (...args) => value.apply(target, args);
|
|
39
42
|
}
|
|
40
|
-
function cloneObject$1(value) {
|
|
41
|
-
return JSON.parse(JSON.stringify(value));
|
|
42
|
-
}
|
|
43
43
|
function cloneValue$1(value) {
|
|
44
44
|
if (Array.isArray(value)) return JSON.parse(JSON.stringify(value));
|
|
45
45
|
if (value && typeof value === "object") return cloneObject$1(value);
|
|
46
46
|
return value;
|
|
47
47
|
}
|
|
48
|
+
function cloneRecord(value) {
|
|
49
|
+
return cloneObject$1(value);
|
|
50
|
+
}
|
|
48
51
|
function parseDataPath$1(path) {
|
|
49
52
|
return path.replace(ARRAY_INDEX_PATH_RE$1, ".$1").split(".").map((segment) => segment.trim()).filter(Boolean);
|
|
50
53
|
}
|
|
@@ -70,13 +73,70 @@ function assignByPath$1(target, path, value) {
|
|
|
70
73
|
const normalizedLeafSegment = isArrayIndexSegment$1(leafSegment) ? Number(leafSegment) : leafSegment;
|
|
71
74
|
current[normalizedLeafSegment] = value;
|
|
72
75
|
}
|
|
76
|
+
function mergeRecord(...records) {
|
|
77
|
+
return Object.assign({}, ...records.filter(Boolean));
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region ../../mpcore/packages/simulator/src/runtime/componentInstance/observers.ts
|
|
81
|
+
function runPropertyObservers(definition, instance, changedKeys, previousProperties) {
|
|
82
|
+
const propOptions = definition.properties;
|
|
83
|
+
if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) return;
|
|
84
|
+
for (const changedKey of changedKeys) {
|
|
85
|
+
const option = propOptions[changedKey];
|
|
86
|
+
if (!option || typeof option !== "object" || Array.isArray(option)) continue;
|
|
87
|
+
const observer = option.observer;
|
|
88
|
+
if (typeof observer !== "function") continue;
|
|
89
|
+
observer.call(instance, instance.properties[changedKey], previousProperties[changedKey]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function normalizeObserverPattern(pattern) {
|
|
93
|
+
return pattern.split(",").map((item) => item.trim()).filter(Boolean);
|
|
94
|
+
}
|
|
95
|
+
function resolveObservedValue(instance, pattern) {
|
|
96
|
+
if (pattern === "**") return {
|
|
97
|
+
...instance.properties,
|
|
98
|
+
...instance.data
|
|
99
|
+
};
|
|
100
|
+
const segments = parseDataPath$1(pattern);
|
|
101
|
+
if (segments.length === 0) return;
|
|
102
|
+
const [rootSegment, ...restSegments] = segments;
|
|
103
|
+
let current = (Object.hasOwn(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
|
|
104
|
+
for (const segment of restSegments) {
|
|
105
|
+
const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
|
|
106
|
+
current = current?.[normalizedSegment];
|
|
107
|
+
}
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
function matchObserverPattern(pattern, changedKeys) {
|
|
111
|
+
if (pattern === "**") return changedKeys.length > 0;
|
|
112
|
+
const patterns = normalizeObserverPattern(pattern);
|
|
113
|
+
return changedKeys.some((changedKey) => {
|
|
114
|
+
return patterns.some((item) => {
|
|
115
|
+
if (item === "**") return true;
|
|
116
|
+
if (changedKey === item) return true;
|
|
117
|
+
return changedKey.startsWith(`${item}.`) || changedKey.startsWith(`${item}[`);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function runComponentObservers(definition, instance, changedKeys, previousProperties = {}) {
|
|
122
|
+
if (changedKeys.length === 0) return;
|
|
123
|
+
runPropertyObservers(definition, instance, changedKeys, previousProperties);
|
|
124
|
+
const observers = definition.observers;
|
|
125
|
+
if (observers && typeof observers === "object" && !Array.isArray(observers)) for (const [pattern, handler] of Object.entries(observers)) {
|
|
126
|
+
if (typeof handler !== "function" || !matchObserverPattern(pattern, changedKeys)) continue;
|
|
127
|
+
const args = normalizeObserverPattern(pattern).map((item) => resolveObservedValue(instance, item));
|
|
128
|
+
handler.call(instance, ...args);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region ../../mpcore/packages/simulator/src/runtime/componentInstance/properties.ts
|
|
73
133
|
function resolveInitialData$1(definition) {
|
|
74
134
|
const rawData = definition.data;
|
|
75
135
|
if (typeof rawData === "function") {
|
|
76
136
|
const next = rawData.call(definition);
|
|
77
|
-
return next && typeof next === "object" && !Array.isArray(next) ?
|
|
137
|
+
return next && typeof next === "object" && !Array.isArray(next) ? cloneRecord(next) : {};
|
|
78
138
|
}
|
|
79
|
-
return rawData && typeof rawData === "object" && !Array.isArray(rawData) ?
|
|
139
|
+
return rawData && typeof rawData === "object" && !Array.isArray(rawData) ? cloneRecord(rawData) : {};
|
|
80
140
|
}
|
|
81
141
|
function isBehaviorDefinition(value) {
|
|
82
142
|
return !!value && typeof value === "object" && !Array.isArray(value) && value.__isHeadlessBehavior__ === true;
|
|
@@ -91,9 +151,6 @@ function flattenBehaviors(definition) {
|
|
|
91
151
|
}
|
|
92
152
|
return flattened;
|
|
93
153
|
}
|
|
94
|
-
function mergeRecord(...records) {
|
|
95
|
-
return Object.assign({}, ...records.filter(Boolean));
|
|
96
|
-
}
|
|
97
154
|
function normalizeComponentDefinition(definition) {
|
|
98
155
|
const behaviors = flattenBehaviors(definition);
|
|
99
156
|
if (behaviors.length === 0) return definition;
|
|
@@ -200,61 +257,13 @@ function resolvePropertyDefaultValue(option) {
|
|
|
200
257
|
if (!option || typeof option !== "object" || Array.isArray(option) || !("value" in option)) return;
|
|
201
258
|
return cloneValue$1(option.value);
|
|
202
259
|
}
|
|
203
|
-
function runPropertyObservers(definition, instance, changedKeys, previousProperties) {
|
|
204
|
-
const propOptions = definition.properties;
|
|
205
|
-
if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) return;
|
|
206
|
-
for (const changedKey of changedKeys) {
|
|
207
|
-
const option = propOptions[changedKey];
|
|
208
|
-
if (!option || typeof option !== "object" || Array.isArray(option)) continue;
|
|
209
|
-
const observer = option.observer;
|
|
210
|
-
if (typeof observer !== "function") continue;
|
|
211
|
-
observer.call(instance, instance.properties[changedKey], previousProperties[changedKey]);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function normalizeObserverPattern(pattern) {
|
|
215
|
-
return pattern.split(",").map((item) => item.trim()).filter(Boolean);
|
|
216
|
-
}
|
|
217
|
-
function resolveObservedValue(instance, pattern) {
|
|
218
|
-
if (pattern === "**") return {
|
|
219
|
-
...instance.properties,
|
|
220
|
-
...instance.data
|
|
221
|
-
};
|
|
222
|
-
const segments = parseDataPath$1(pattern);
|
|
223
|
-
if (segments.length === 0) return;
|
|
224
|
-
const [rootSegment, ...restSegments] = segments;
|
|
225
|
-
let current = (Object.hasOwn(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
|
|
226
|
-
for (const segment of restSegments) {
|
|
227
|
-
const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
|
|
228
|
-
current = current?.[normalizedSegment];
|
|
229
|
-
}
|
|
230
|
-
return current;
|
|
231
|
-
}
|
|
232
|
-
function matchObserverPattern(pattern, changedKeys) {
|
|
233
|
-
if (pattern === "**") return changedKeys.length > 0;
|
|
234
|
-
const patterns = normalizeObserverPattern(pattern);
|
|
235
|
-
return changedKeys.some((changedKey) => {
|
|
236
|
-
return patterns.some((item) => {
|
|
237
|
-
if (item === "**") return true;
|
|
238
|
-
if (changedKey === item) return true;
|
|
239
|
-
return changedKey.startsWith(`${item}.`) || changedKey.startsWith(`${item}[`);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
function runComponentObservers(definition, instance, changedKeys, previousProperties = {}) {
|
|
244
|
-
if (changedKeys.length === 0) return;
|
|
245
|
-
runPropertyObservers(definition, instance, changedKeys, previousProperties);
|
|
246
|
-
const observers = definition.observers;
|
|
247
|
-
if (observers && typeof observers === "object" && !Array.isArray(observers)) for (const [pattern, handler] of Object.entries(observers)) {
|
|
248
|
-
if (typeof handler !== "function" || !matchObserverPattern(pattern, changedKeys)) continue;
|
|
249
|
-
const args = normalizeObserverPattern(pattern).map((item) => resolveObservedValue(instance, item));
|
|
250
|
-
handler.call(instance, ...args);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
260
|
function normalizeComponentPropertyValue(definition, key, rawValue) {
|
|
254
261
|
const option = definition.properties?.[key];
|
|
255
262
|
if (rawValue == null) return resolvePropertyDefaultValue(option);
|
|
256
263
|
return coerceComponentPropertyValue(rawValue, option);
|
|
257
264
|
}
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region ../../mpcore/packages/simulator/src/runtime/componentInstance/index.ts
|
|
258
267
|
function createComponentInstance(options) {
|
|
259
268
|
const definition = normalizeComponentDefinition(options.definition);
|
|
260
269
|
const instance = {
|
|
@@ -2830,9 +2839,11 @@ function parseDocument(data, options) {
|
|
|
2830
2839
|
return handler.root;
|
|
2831
2840
|
}
|
|
2832
2841
|
//#endregion
|
|
2833
|
-
//#region ../../mpcore/packages/simulator/src/runtime/render.ts
|
|
2834
|
-
const LEADING_SLASH_RE$4 = /^\/+/;
|
|
2842
|
+
//#region ../../mpcore/packages/simulator/src/runtime/render/shared.ts
|
|
2835
2843
|
const TEMPLATE_INTERPOLATION_RE$1 = /\{\{([^{}]+)\}\}/g;
|
|
2844
|
+
const DATASET_NAME_RE$3 = /-([a-z])/g;
|
|
2845
|
+
const BRACKET_INDEX_RE = /\[(\d+)\]/g;
|
|
2846
|
+
const LEADING_SLASH_RE$4 = /^\/+/;
|
|
2836
2847
|
const EVENT_BINDING_ATTRS = [
|
|
2837
2848
|
"bindtap",
|
|
2838
2849
|
"bind:tap",
|
|
@@ -2855,8 +2866,6 @@ const STRUCTURAL_ATTRS = [
|
|
|
2855
2866
|
"wx:key"
|
|
2856
2867
|
];
|
|
2857
2868
|
const WX_ELSE_ATTRS = new Set(["wx:elif", "wx:else"]);
|
|
2858
|
-
const DATASET_NAME_RE$3 = /-([a-z])/g;
|
|
2859
|
-
const BRACKET_INDEX_RE = /\[(\d+)\]/g;
|
|
2860
2869
|
const CLASS_SPLIT_RE = /\s+/;
|
|
2861
2870
|
const JS_FILE_RE = /\.js$/;
|
|
2862
2871
|
function isMustacheOnly(value) {
|
|
@@ -2964,21 +2973,46 @@ function resolveComponentAttributeValue(value, scope) {
|
|
|
2964
2973
|
}
|
|
2965
2974
|
return interpolateTemplate$1(value, scope.data);
|
|
2966
2975
|
}
|
|
2967
|
-
function
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
+
function applyNodeBindings(node, scope) {
|
|
2977
|
+
if (!isTagNode(node)) {
|
|
2978
|
+
if (node.type === "text" && typeof node.data === "string") node.data = interpolateTemplate$1(node.data, scope.data);
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
node.attribs ??= {};
|
|
2982
|
+
node.attribs["data-sim-scope"] = scope.getScopeId();
|
|
2983
|
+
for (const key of STRUCTURAL_ATTRS) delete node.attribs[key];
|
|
2984
|
+
for (const [key, value] of Object.entries({ ...node.attribs })) {
|
|
2985
|
+
if (EVENT_BINDING_ATTRS.includes(key)) {
|
|
2986
|
+
node.attribs["data-sim-tap"] = value;
|
|
2987
|
+
continue;
|
|
2976
2988
|
}
|
|
2977
|
-
|
|
2978
|
-
} catch {
|
|
2979
|
-
return /* @__PURE__ */ new Map();
|
|
2989
|
+
node.attribs[key] = typeof value === "string" ? String(resolveAttributeValue(value, scope)) : String(value);
|
|
2980
2990
|
}
|
|
2981
2991
|
}
|
|
2992
|
+
function createMergedScopeData(pageData, componentProperties, componentData) {
|
|
2993
|
+
return {
|
|
2994
|
+
...pageData,
|
|
2995
|
+
...componentProperties,
|
|
2996
|
+
...componentData
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
function evaluateConditionalBranch(node, scope) {
|
|
3000
|
+
const condition = node.attribs?.["wx:if"] ?? node.attribs?.["wx:elif"];
|
|
3001
|
+
if (condition == null) return true;
|
|
3002
|
+
return Boolean(resolveRawValueByPath(scope.data, condition));
|
|
3003
|
+
}
|
|
3004
|
+
function createLoopScope(scope, itemName, indexName, item, index) {
|
|
3005
|
+
return {
|
|
3006
|
+
...scope,
|
|
3007
|
+
data: {
|
|
3008
|
+
...scope.data,
|
|
3009
|
+
[indexName]: index,
|
|
3010
|
+
[itemName]: item
|
|
3011
|
+
}
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
//#endregion
|
|
3015
|
+
//#region ../../mpcore/packages/simulator/src/runtime/render/component.ts
|
|
2982
3016
|
function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, alias) {
|
|
2983
3017
|
const componentBasePath = resolveUsingComponents(ownerJsonPath, ownerFilePath).get(alias);
|
|
2984
3018
|
if (!componentBasePath) return null;
|
|
@@ -2993,6 +3027,21 @@ function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, al
|
|
|
2993
3027
|
absoluteTemplatePath
|
|
2994
3028
|
};
|
|
2995
3029
|
}
|
|
3030
|
+
function resolveUsingComponents(ownerJsonPath, ownerFilePath) {
|
|
3031
|
+
try {
|
|
3032
|
+
const usingComponents = JSON.parse(fs.readFileSync(ownerJsonPath, "utf8")).usingComponents;
|
|
3033
|
+
if (!usingComponents || typeof usingComponents !== "object" || Array.isArray(usingComponents)) return /* @__PURE__ */ new Map();
|
|
3034
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
3035
|
+
for (const [alias, rawPath] of Object.entries(usingComponents)) {
|
|
3036
|
+
if (typeof rawPath !== "string") continue;
|
|
3037
|
+
const basePath = rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath));
|
|
3038
|
+
resolved.set(alias, basePath.replace(LEADING_SLASH_RE$4, ""));
|
|
3039
|
+
}
|
|
3040
|
+
return resolved;
|
|
3041
|
+
} catch {
|
|
3042
|
+
return /* @__PURE__ */ new Map();
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
2996
3045
|
function collectComponentEventBindings(hostNode) {
|
|
2997
3046
|
const eventBindings = /* @__PURE__ */ new Map();
|
|
2998
3047
|
for (const [key, value] of Object.entries(hostNode.attribs ?? {})) {
|
|
@@ -3043,22 +3092,6 @@ function buildComponentTrigger(componentScopeId, context, hostNode) {
|
|
|
3043
3092
|
}
|
|
3044
3093
|
};
|
|
3045
3094
|
}
|
|
3046
|
-
function applyNodeBindings(node, scope) {
|
|
3047
|
-
if (!isTagNode(node)) {
|
|
3048
|
-
if (node.type === "text" && typeof node.data === "string") node.data = interpolateTemplate$1(node.data, scope.data);
|
|
3049
|
-
return;
|
|
3050
|
-
}
|
|
3051
|
-
node.attribs ??= {};
|
|
3052
|
-
node.attribs["data-sim-scope"] = scope.getScopeId();
|
|
3053
|
-
for (const key of STRUCTURAL_ATTRS) delete node.attribs[key];
|
|
3054
|
-
for (const [key, value] of Object.entries({ ...node.attribs })) {
|
|
3055
|
-
if (EVENT_BINDING_ATTRS.includes(key)) {
|
|
3056
|
-
node.attribs["data-sim-tap"] = value;
|
|
3057
|
-
continue;
|
|
3058
|
-
}
|
|
3059
|
-
node.attribs[key] = typeof value === "string" ? String(resolveAttributeValue(value, scope)) : String(value);
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
3095
|
function syncComponentProperties(instance, definition, nextProperties, bindingExpressions, changedPageKeys) {
|
|
3063
3096
|
const changedRootKeys = [];
|
|
3064
3097
|
const previousProperties = {};
|
|
@@ -3081,31 +3114,62 @@ function syncComponentProperties(instance, definition, nextProperties, bindingEx
|
|
|
3081
3114
|
if (changedRootKeys.length === 0) return;
|
|
3082
3115
|
runComponentObservers(definition, instance, changedRootKeys, previousProperties);
|
|
3083
3116
|
}
|
|
3084
|
-
function
|
|
3117
|
+
function createComponentScope(clonedNode, scope, componentScopeId, componentInstance) {
|
|
3118
|
+
const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
|
|
3085
3119
|
return {
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3120
|
+
alias: clonedNode.name,
|
|
3121
|
+
classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
|
|
3122
|
+
data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
|
|
3123
|
+
dataset: collectDataset$3(clonedNode),
|
|
3124
|
+
eventBindings: collectComponentEventBindings(clonedNode),
|
|
3125
|
+
getMethod: (methodName) => {
|
|
3126
|
+
const method = componentInstance?.[methodName];
|
|
3127
|
+
return typeof method === "function" ? method : void 0;
|
|
3128
|
+
},
|
|
3129
|
+
getScopeId: () => componentScopeId,
|
|
3130
|
+
hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
|
|
3131
|
+
id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
|
|
3132
|
+
listenerScopeId: scope.getScopeId(),
|
|
3133
|
+
ownerScopeId
|
|
3089
3134
|
};
|
|
3090
3135
|
}
|
|
3091
|
-
function
|
|
3092
|
-
|
|
3093
|
-
}
|
|
3094
|
-
|
|
3136
|
+
function resolveComponentProperties(clonedNode, scope) {
|
|
3137
|
+
const nextProperties = {};
|
|
3138
|
+
const bindingExpressions = {};
|
|
3139
|
+
for (const [key, value] of Object.entries(clonedNode.attribs ?? {})) {
|
|
3140
|
+
if (key.startsWith("bind")) continue;
|
|
3141
|
+
if (isMustacheOnly(String(value))) bindingExpressions[key] = String(value).trim().slice(2, -2).trim();
|
|
3142
|
+
nextProperties[key] = resolveComponentAttributeValue(String(value), scope);
|
|
3143
|
+
}
|
|
3095
3144
|
return {
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
...scope.data,
|
|
3099
|
-
[indexName]: index,
|
|
3100
|
-
[itemName]: item
|
|
3101
|
-
}
|
|
3145
|
+
nextProperties,
|
|
3146
|
+
bindingExpressions
|
|
3102
3147
|
};
|
|
3103
3148
|
}
|
|
3104
|
-
function
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3107
|
-
|
|
3149
|
+
function createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId) {
|
|
3150
|
+
const componentInstance = createComponentInstance({
|
|
3151
|
+
definition: componentEntry.definition,
|
|
3152
|
+
properties: nextProperties,
|
|
3153
|
+
triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
|
|
3154
|
+
});
|
|
3155
|
+
componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
|
|
3156
|
+
componentInstance.createMediaQueryObserver = () => context.session.createMediaQueryObserver(componentInstance);
|
|
3157
|
+
componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
|
|
3158
|
+
componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
|
|
3159
|
+
componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
|
|
3160
|
+
runComponentLifecycle(componentInstance, "created");
|
|
3161
|
+
runComponentObservers(componentInstance.__definition__ ?? componentEntry.definition, componentInstance, Object.keys(nextProperties), {});
|
|
3162
|
+
componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
|
|
3163
|
+
runComponentLifecycle(componentInstance, "attached");
|
|
3164
|
+
context.componentCache.set(componentScopeId, componentInstance);
|
|
3165
|
+
return componentInstance;
|
|
3166
|
+
}
|
|
3167
|
+
function renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes) {
|
|
3168
|
+
const componentDocument = parseTemplateDocument(readTemplateSource(componentEntry.absoluteTemplatePath));
|
|
3169
|
+
return renderNodeTree((componentDocument.children ?? [])[0] ?? componentDocument, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes);
|
|
3108
3170
|
}
|
|
3171
|
+
//#endregion
|
|
3172
|
+
//#region ../../mpcore/packages/simulator/src/runtime/render/index.ts
|
|
3109
3173
|
function expandNodeByFor(node, scope) {
|
|
3110
3174
|
const forExpression = node.attribs?.["wx:for"];
|
|
3111
3175
|
if (!forExpression) return [{
|
|
@@ -3175,51 +3239,14 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
|
|
|
3175
3239
|
if (componentEntry) {
|
|
3176
3240
|
const componentScopeId = `${instancePath}/${clonedNode.name}`;
|
|
3177
3241
|
const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
|
|
3178
|
-
const nextProperties =
|
|
3179
|
-
const bindingExpressions = {};
|
|
3180
|
-
for (const [key, value] of Object.entries(clonedNode.attribs ?? {})) {
|
|
3181
|
-
if (key.startsWith("bind")) continue;
|
|
3182
|
-
if (isMustacheOnly(String(value))) bindingExpressions[key] = String(value).trim().slice(2, -2).trim();
|
|
3183
|
-
nextProperties[key] = resolveComponentAttributeValue(String(value), scope);
|
|
3184
|
-
}
|
|
3242
|
+
const { nextProperties, bindingExpressions } = resolveComponentProperties(clonedNode, scope);
|
|
3185
3243
|
let componentInstance = context.componentCache.get(componentScopeId);
|
|
3186
|
-
if (!componentInstance)
|
|
3187
|
-
|
|
3188
|
-
definition: componentEntry.definition,
|
|
3189
|
-
properties: nextProperties,
|
|
3190
|
-
triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
|
|
3191
|
-
});
|
|
3192
|
-
componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
|
|
3193
|
-
componentInstance.createMediaQueryObserver = () => context.session.createMediaQueryObserver(componentInstance);
|
|
3194
|
-
componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
|
|
3195
|
-
componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
|
|
3196
|
-
componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
|
|
3197
|
-
runComponentLifecycle(componentInstance, "created");
|
|
3198
|
-
runComponentObservers(componentInstance.__definition__ ?? componentEntry.definition, componentInstance, Object.keys(nextProperties), {});
|
|
3199
|
-
componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
|
|
3200
|
-
runComponentLifecycle(componentInstance, "attached");
|
|
3201
|
-
context.componentCache.set(componentScopeId, componentInstance);
|
|
3202
|
-
} else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
|
|
3244
|
+
if (!componentInstance) componentInstance = createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId);
|
|
3245
|
+
else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
|
|
3203
3246
|
seenComponentScopes.add(componentScopeId);
|
|
3204
|
-
const componentScope =
|
|
3205
|
-
alias: clonedNode.name,
|
|
3206
|
-
classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
|
|
3207
|
-
data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
|
|
3208
|
-
dataset: collectDataset$3(clonedNode),
|
|
3209
|
-
eventBindings: collectComponentEventBindings(clonedNode),
|
|
3210
|
-
getMethod: (methodName) => {
|
|
3211
|
-
const method = componentInstance?.[methodName];
|
|
3212
|
-
return typeof method === "function" ? method : void 0;
|
|
3213
|
-
},
|
|
3214
|
-
getScopeId: () => componentScopeId,
|
|
3215
|
-
hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
|
|
3216
|
-
id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
|
|
3217
|
-
listenerScopeId: scope.getScopeId(),
|
|
3218
|
-
ownerScopeId
|
|
3219
|
-
};
|
|
3247
|
+
const componentScope = createComponentScope(clonedNode, scope, componentScopeId, componentInstance);
|
|
3220
3248
|
context.componentScopes.set(componentScopeId, componentScope);
|
|
3221
|
-
const
|
|
3222
|
-
const renderedComponentRoot = renderNodeTree((componentDocument.children ?? [])[0] ?? componentDocument, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes);
|
|
3249
|
+
const renderedComponentRoot = renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes);
|
|
3223
3250
|
if (renderedComponentRoot.attribs) renderedComponentRoot.attribs["data-sim-component"] = clonedNode.name;
|
|
3224
3251
|
if (!componentInstance.__ready__) {
|
|
3225
3252
|
runComponentLifecycle(componentInstance, "ready");
|
|
@@ -3291,7 +3318,7 @@ function registerComponentDefinition(registries, definition) {
|
|
|
3291
3318
|
return definition;
|
|
3292
3319
|
}
|
|
3293
3320
|
//#endregion
|
|
3294
|
-
//#region ../../mpcore/packages/simulator/src/host/wx.ts
|
|
3321
|
+
//#region ../../mpcore/packages/simulator/src/host/wx/index.ts
|
|
3295
3322
|
function invokeWxApi(operation, option) {
|
|
3296
3323
|
try {
|
|
3297
3324
|
const result = operation();
|
|
@@ -3534,10 +3561,7 @@ function createHeadlessWx(driver) {
|
|
|
3534
3561
|
chooseMedia: (option) => invokeWxApi(() => driver.chooseMedia(option ?? {}), option),
|
|
3535
3562
|
chooseVideo: (option) => invokeWxApi(() => driver.chooseVideo(option ?? {}), option),
|
|
3536
3563
|
compressImage: (option) => invokeWxApi(() => driver.compressImage(option), option),
|
|
3537
|
-
clearStorage: (option) => invokeWxApi(() => {
|
|
3538
|
-
driver.clearStorageSync();
|
|
3539
|
-
return { errMsg: "clearStorage:ok" };
|
|
3540
|
-
}, option),
|
|
3564
|
+
clearStorage: (option) => invokeWxApi(() => ({ errMsg: (driver.clearStorageSync(), "clearStorage:ok") }), option),
|
|
3541
3565
|
clearStorageSync: () => driver.clearStorageSync(),
|
|
3542
3566
|
createAnimation: (option) => driver.createAnimation(option),
|
|
3543
3567
|
createCanvasContext: (canvasId, component) => driver.createCanvasContext(canvasId, component),
|
|
@@ -5515,10 +5539,12 @@ function inferImageType(filePath, fileContent) {
|
|
|
5515
5539
|
}
|
|
5516
5540
|
function inferImageSize(fileContent) {
|
|
5517
5541
|
try {
|
|
5518
|
-
const
|
|
5542
|
+
const config = JSON.parse(fileContent)?.config;
|
|
5543
|
+
const destHeight = config && "destHeight" in config ? config.destHeight : void 0;
|
|
5544
|
+
const destWidth = config && "destWidth" in config ? config.destWidth : void 0;
|
|
5519
5545
|
return {
|
|
5520
|
-
height:
|
|
5521
|
-
width:
|
|
5546
|
+
height: destHeight ?? config?.height ?? 0,
|
|
5547
|
+
width: destWidth ?? config?.width ?? 0
|
|
5522
5548
|
};
|
|
5523
5549
|
} catch {
|
|
5524
5550
|
return {
|
|
@@ -6299,7 +6325,7 @@ function createHeadlessWxState() {
|
|
|
6299
6325
|
width
|
|
6300
6326
|
};
|
|
6301
6327
|
}),
|
|
6302
|
-
type: mediaTypes.length > 1 ? "mix" : mediaTypes[0]
|
|
6328
|
+
type: mediaTypes.length > 1 ? "mix" : mediaTypes[0] ?? "image"
|
|
6303
6329
|
};
|
|
6304
6330
|
},
|
|
6305
6331
|
chooseVideo(option) {
|
|
@@ -7826,7 +7852,7 @@ var HeadlessTestingPageHandle = class {
|
|
|
7826
7852
|
}
|
|
7827
7853
|
};
|
|
7828
7854
|
//#endregion
|
|
7829
|
-
//#region ../../mpcore/packages/simulator/src/testing/sessionHandle.ts
|
|
7855
|
+
//#region ../../mpcore/packages/simulator/src/testing/sessionHandle/shared.ts
|
|
7830
7856
|
const LEADING_SLASH_RE = /^\/+/;
|
|
7831
7857
|
const DEFAULT_WAIT_TIMEOUT = 1e3;
|
|
7832
7858
|
const DEFAULT_WAIT_INTERVAL = 10;
|
|
@@ -7837,6 +7863,28 @@ function resolvePageScopeId(scopeId) {
|
|
|
7837
7863
|
const pagePathIndex = scopeId.indexOf("/page/");
|
|
7838
7864
|
return pagePathIndex >= 0 ? scopeId.slice(0, pagePathIndex) : scopeId;
|
|
7839
7865
|
}
|
|
7866
|
+
function normalizeNonEmptyInput(value, label) {
|
|
7867
|
+
const normalizedValue = value.trim();
|
|
7868
|
+
if (!normalizedValue) throw new Error(`${label} must be a non-empty string in headless testing runtime.`);
|
|
7869
|
+
return normalizedValue;
|
|
7870
|
+
}
|
|
7871
|
+
async function waitForDelay(ms = 0) {
|
|
7872
|
+
if (ms <= 0) return;
|
|
7873
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
7874
|
+
}
|
|
7875
|
+
async function pollUntil(check, errorMessage, options = {}) {
|
|
7876
|
+
const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? DEFAULT_WAIT_TIMEOUT)) : DEFAULT_WAIT_TIMEOUT;
|
|
7877
|
+
const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? DEFAULT_WAIT_INTERVAL)) : DEFAULT_WAIT_INTERVAL;
|
|
7878
|
+
const deadline = Date.now() + timeout;
|
|
7879
|
+
while (true) {
|
|
7880
|
+
const result = await check();
|
|
7881
|
+
if (result != null) return result;
|
|
7882
|
+
if (Date.now() >= deadline) throw new Error(errorMessage);
|
|
7883
|
+
await waitForDelay(interval);
|
|
7884
|
+
}
|
|
7885
|
+
}
|
|
7886
|
+
//#endregion
|
|
7887
|
+
//#region ../../mpcore/packages/simulator/src/testing/sessionHandle/scope.ts
|
|
7840
7888
|
var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
|
|
7841
7889
|
constructor(scopeId, project, session) {
|
|
7842
7890
|
this.scopeId = scopeId;
|
|
@@ -7848,8 +7896,7 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
|
|
|
7848
7896
|
return scopeId ? new HeadlessTestingScopeHandle(scopeId, this.project, this.session) : null;
|
|
7849
7897
|
}
|
|
7850
7898
|
async callMethod(methodName, ...args) {
|
|
7851
|
-
const normalizedMethodName = methodName
|
|
7852
|
-
if (!normalizedMethodName) throw new Error("Method name must be a non-empty string in headless testing runtime.");
|
|
7899
|
+
const normalizedMethodName = normalizeNonEmptyInput(methodName, "Method name");
|
|
7853
7900
|
return this.session.callScopeMethodDirect(this.scopeId, normalizedMethodName, ...args);
|
|
7854
7901
|
}
|
|
7855
7902
|
async snapshot() {
|
|
@@ -7866,75 +7913,37 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
|
|
|
7866
7913
|
return this.createScopeHandle(this.session.selectOwnerComponent(this.scopeId));
|
|
7867
7914
|
}
|
|
7868
7915
|
async waitForOwnerComponent(options = {}) {
|
|
7869
|
-
|
|
7870
|
-
const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
|
|
7871
|
-
const deadline = Date.now() + timeout;
|
|
7872
|
-
while (true) {
|
|
7873
|
-
const owner = await this.ownerComponent();
|
|
7874
|
-
if (owner) return owner;
|
|
7875
|
-
if (Date.now() >= deadline) throw new Error("Timed out waiting for owner component in headless testing runtime.");
|
|
7876
|
-
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
7877
|
-
}
|
|
7916
|
+
return await pollUntil(async () => await this.ownerComponent(), "Timed out waiting for owner component in headless testing runtime.", options);
|
|
7878
7917
|
}
|
|
7879
7918
|
async selectComponent(selector) {
|
|
7880
|
-
const normalizedSelector = selector
|
|
7881
|
-
if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
|
|
7919
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7882
7920
|
const component = this.session.selectComponentWithin(this.scopeId, normalizedSelector);
|
|
7883
7921
|
return this.createScopeHandle(component);
|
|
7884
7922
|
}
|
|
7885
7923
|
async selectAllComponents(selector) {
|
|
7886
|
-
const normalizedSelector = selector
|
|
7887
|
-
if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
|
|
7924
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7888
7925
|
return this.session.selectAllComponentsWithin(this.scopeId, normalizedSelector).map((component) => this.createScopeHandle(component)).filter((handle) => Boolean(handle));
|
|
7889
7926
|
}
|
|
7890
7927
|
async waitForComponent(selector, options = {}) {
|
|
7891
|
-
const normalizedSelector = selector
|
|
7892
|
-
|
|
7893
|
-
const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
|
|
7894
|
-
const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
|
|
7895
|
-
const deadline = Date.now() + timeout;
|
|
7896
|
-
while (true) {
|
|
7897
|
-
const component = await this.selectComponent(normalizedSelector);
|
|
7898
|
-
if (component) return component;
|
|
7899
|
-
if (Date.now() >= deadline) throw new Error(`Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`);
|
|
7900
|
-
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
7901
|
-
}
|
|
7928
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7929
|
+
return await pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
|
|
7902
7930
|
}
|
|
7903
7931
|
async waitForComponents(selector, count = 1, options = {}) {
|
|
7904
|
-
const normalizedSelector = selector
|
|
7905
|
-
if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
|
|
7932
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7906
7933
|
const normalizedCount = Number.isFinite(count) ? Math.max(1, Math.trunc(count)) : 1;
|
|
7907
|
-
|
|
7908
|
-
const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
|
|
7909
|
-
const deadline = Date.now() + timeout;
|
|
7910
|
-
while (true) {
|
|
7934
|
+
return await pollUntil(async () => {
|
|
7911
7935
|
const components = await this.selectAllComponents(normalizedSelector);
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
7915
|
-
}
|
|
7936
|
+
return components.length >= normalizedCount ? components : null;
|
|
7937
|
+
}, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
|
|
7916
7938
|
}
|
|
7917
7939
|
};
|
|
7940
|
+
//#endregion
|
|
7941
|
+
//#region ../../mpcore/packages/simulator/src/testing/sessionHandle/index.ts
|
|
7918
7942
|
var HeadlessTestingSessionHandle = class {
|
|
7919
7943
|
constructor(project, session) {
|
|
7920
7944
|
this.project = project;
|
|
7921
7945
|
this.session = session;
|
|
7922
7946
|
}
|
|
7923
|
-
async waitFor(ms = 0) {
|
|
7924
|
-
if (ms <= 0) return;
|
|
7925
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
7926
|
-
}
|
|
7927
|
-
async pollUntil(check, errorMessage, options = {}) {
|
|
7928
|
-
const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? DEFAULT_WAIT_TIMEOUT)) : DEFAULT_WAIT_TIMEOUT;
|
|
7929
|
-
const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? DEFAULT_WAIT_INTERVAL)) : DEFAULT_WAIT_INTERVAL;
|
|
7930
|
-
const deadline = Date.now() + timeout;
|
|
7931
|
-
while (true) {
|
|
7932
|
-
const result = await check();
|
|
7933
|
-
if (result != null) return result;
|
|
7934
|
-
if (Date.now() >= deadline) throw new Error(errorMessage);
|
|
7935
|
-
await this.waitFor(interval);
|
|
7936
|
-
}
|
|
7937
|
-
}
|
|
7938
7947
|
async close() {}
|
|
7939
7948
|
async currentPage() {
|
|
7940
7949
|
const current = this.session.getCurrentPages().at(-1);
|
|
@@ -7944,32 +7953,27 @@ var HeadlessTestingSessionHandle = class {
|
|
|
7944
7953
|
return this.session.getCurrentPages().map((page) => new HeadlessTestingPageHandle(this.project, page, this.session));
|
|
7945
7954
|
}
|
|
7946
7955
|
async scopeSnapshot(scopeId) {
|
|
7947
|
-
const normalizedScopeId = scopeId
|
|
7948
|
-
if (!normalizedScopeId) throw new Error("Scope id must be a non-empty string in headless testing runtime.");
|
|
7956
|
+
const normalizedScopeId = normalizeNonEmptyInput(scopeId, "Scope id");
|
|
7949
7957
|
return this.session.getScopeSnapshot(normalizedScopeId);
|
|
7950
7958
|
}
|
|
7951
7959
|
async selectComponent(selector) {
|
|
7952
|
-
const normalizedSelector = selector
|
|
7953
|
-
if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
|
|
7960
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7954
7961
|
const component = this.session.selectComponent(normalizedSelector);
|
|
7955
7962
|
const scopeId = this.session.getScopeIdForComponent(component);
|
|
7956
7963
|
return scopeId ? new HeadlessTestingScopeHandle(scopeId, this.project, this.session) : null;
|
|
7957
7964
|
}
|
|
7958
7965
|
async selectAllComponents(selector) {
|
|
7959
|
-
const normalizedSelector = selector
|
|
7960
|
-
if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
|
|
7966
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7961
7967
|
return this.session.selectAllComponents(normalizedSelector).map((component) => this.session.getScopeIdForComponent(component)).filter((scopeId) => Boolean(scopeId)).map((scopeId) => new HeadlessTestingScopeHandle(scopeId, this.project, this.session));
|
|
7962
7968
|
}
|
|
7963
7969
|
async waitForComponent(selector, options = {}) {
|
|
7964
|
-
const normalizedSelector = selector
|
|
7965
|
-
|
|
7966
|
-
return await this.pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
|
|
7970
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7971
|
+
return await pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
|
|
7967
7972
|
}
|
|
7968
7973
|
async waitForComponents(selector, count = 1, options = {}) {
|
|
7969
|
-
const normalizedSelector = selector
|
|
7970
|
-
if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
|
|
7974
|
+
const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
|
|
7971
7975
|
const normalizedCount = Number.isFinite(count) ? Math.max(1, Math.trunc(count)) : 1;
|
|
7972
|
-
return await
|
|
7976
|
+
return await pollUntil(async () => {
|
|
7973
7977
|
const components = await this.selectAllComponents(normalizedSelector);
|
|
7974
7978
|
return components.length >= normalizedCount ? components : null;
|
|
7975
7979
|
}, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
|
|
@@ -7981,7 +7985,7 @@ var HeadlessTestingSessionHandle = class {
|
|
|
7981
7985
|
async waitForCurrentPage(route, options = {}) {
|
|
7982
7986
|
const normalizedRoute = route?.trim();
|
|
7983
7987
|
if (normalizedRoute != null && !normalizedRoute) throw new Error("Route must be a non-empty string in headless testing runtime.");
|
|
7984
|
-
return await
|
|
7988
|
+
return await pollUntil(async () => {
|
|
7985
7989
|
const currentPageInstance = this.session.getCurrentPages().at(-1);
|
|
7986
7990
|
if (!currentPageInstance) return null;
|
|
7987
7991
|
const current = new HeadlessTestingPageHandle(this.project, currentPageInstance, this.session);
|
package/package.json
CHANGED