统一微信壳桥接目录

新增 miniprogram/shell 承接微信 Page 生命周期与 wx 容器调用

收窄微信页面入口为 shell 页面工厂装配

更新三端 host-bridge/shell 目录门禁与认证 smoke

补充微信 shell 测试与 CommonJS 测试加载工具

同步宿主壳架构文档和项目共享记忆

补齐 api-server 外部生成测试 fixture 更新时间字段
This commit is contained in:
2026-06-18 16:32:25 +08:00
parent 00b41b6a29
commit 7609c37d5f
24 changed files with 1449 additions and 1022 deletions
+32 -4
View File
@@ -17,6 +17,16 @@ const expectedWechatHostBridgeFiles = [
'wechatHostBridgeWebView.js',
'wechatHostBridgeWebView.test.js',
];
const expectedWechatShellFiles = [
'wechatShellPayment.js',
'wechatShellPayment.test.js',
'wechatShellShareGrid.js',
'wechatShellShareGrid.test.js',
'wechatShellSubscribeMessage.js',
'wechatShellSubscribeMessage.test.js',
'wechatShellWebView.js',
'wechatShellWebView.test.js',
];
const expectedMobileHostBridgeFiles = [
'mobileHostBridge.test.ts',
'mobileHostBridge.ts',
@@ -210,15 +220,33 @@ function assertHostBridgeLayerLayout() {
'wechat host bridge files',
);
for (const pagePath of [
const wechatShellFiles = fs
.readdirSync('miniprogram/shell', { withFileTypes: true })
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
.sort();
assertSameList(
wechatShellFiles,
expectedWechatShellFiles,
'wechat shell files',
);
const wechatPagePaths = [
'miniprogram/pages/web-view/index.js',
'miniprogram/pages/wechat-pay/index.js',
'miniprogram/pages/share-grid/index.js',
'miniprogram/pages/subscribe-message/index.js',
]) {
];
for (const pagePath of wechatPagePaths) {
const source = fs.readFileSync(pagePath, 'utf8');
if (source.includes("require('./index.shared')")) {
throw new Error(`${pagePath} must import from miniprogram/host-bridge`);
if (!source.includes("require('../../shell/")) {
throw new Error(`${pagePath} must import from miniprogram/shell`);
}
if (
source.includes("require('../../host-bridge/") ||
source.includes("require('./index.shared')")
) {
throw new Error(`${pagePath} must not import bridge logic directly`);
}
}
@@ -54,22 +54,27 @@ if (failures.length > 0) {
console.log('\n[wechat-miniprogram-auth-smoke] 通过');
function checkMiniProgramShell() {
const shellPath = join(repoRoot, 'miniprogram', 'pages', 'web-view', 'index.js');
const shellPath = join(repoRoot, 'miniprogram', 'shell', 'wechatShellWebView.js');
const webViewBridgePath = join(repoRoot, 'miniprogram', 'host-bridge', 'wechatHostBridgeWebView.js');
const pagePath = join(repoRoot, 'miniprogram', 'pages', 'web-view', 'index.js');
const shellTemplatePath = join(repoRoot, 'miniprogram', 'pages', 'web-view', 'index.wxml');
const authServiceTestPath = join(repoRoot, 'src', 'services', 'authService.test.ts');
ensureNeedles(pagePath, ["require('../../shell/wechatShellWebView')"]);
ensureNeedles(shellPath, [
'/api/auth/wechat/miniprogram-login',
'/api/auth/wechat/bind-phone',
"'x-client-type': MINI_PROGRAM_CLIENT_TYPE",
"'x-client-runtime': MINI_PROGRAM_CLIENT_RUNTIME",
'auth_provider',
'auth_token',
'auth_binding_status',
'bindingStatus',
'pending_bind_phone',
'wechatPhoneCode',
]);
ensureNeedles(webViewBridgePath, [
'auth_provider',
'auth_token',
'auth_binding_status',
]);
ensureNeedles(shellTemplatePath, ['getPhoneNumber', 'bindgetphonenumber']);
+61 -41
View File
@@ -5,12 +5,17 @@ import vm from 'node:vm';
import { beforeEach, describe, expect, test, vi } from 'vitest';
const repoRoot = process.cwd();
const pageScriptPath = join(
const shellScriptPath = join(
repoRoot,
'miniprogram',
'pages',
'web-view',
'index.js',
'shell',
'wechatShellWebView.js',
);
const webViewHostBridgePath = join(
repoRoot,
'miniprogram',
'host-bridge',
'wechatHostBridgeWebView.js',
);
type MiniProgramPage = {
@@ -43,45 +48,28 @@ function loadWebViewPage(
wxMock: ReturnType<typeof createWxMock>,
configOverrides: Record<string, unknown> = {},
) {
let pageConfig: Record<string, unknown> | null = null;
const source = readFileSync(pageScriptPath, 'utf8');
const sandbox = {
console,
getCurrentPages: () => [],
module: { exports: {} },
Page(config: Record<string, unknown>) {
pageConfig = config;
(globalThis as unknown as { wx: ReturnType<typeof createWxMock> }).wx =
wxMock;
const webViewBridge = loadCommonJsModule(webViewHostBridgePath, {});
const shellModule = loadCommonJsModule(shellScriptPath, {
'../config': {
API_BASE_URL: 'https://www.genarrative.world/',
DEV_API_BASE_URL: 'https://dev.genarrative.world/',
DEV_WEB_VIEW_ENTRY_URL: 'https://dev.genarrative.world/',
MINI_PROGRAM_APP_ID: 'wx-test-app',
MINI_PROGRAM_ENV: 'release',
WEB_VIEW_ENTRY_URL: 'https://www.genarrative.world/',
WEB_VIEW_SOURCE_QUERY: {
clientType: 'mini_program',
clientRuntime: 'wechat_mini_program',
},
...configOverrides,
},
setTimeout(callback: () => void) {
callback();
return 1;
},
require(requestPath: string) {
if (requestPath === '../../config') {
return {
API_BASE_URL: 'https://www.genarrative.world/',
DEV_API_BASE_URL: 'https://dev.genarrative.world/',
DEV_WEB_VIEW_ENTRY_URL: 'https://dev.genarrative.world/',
MINI_PROGRAM_APP_ID: 'wx-test-app',
MINI_PROGRAM_ENV: 'release',
WEB_VIEW_ENTRY_URL: 'https://www.genarrative.world/',
WEB_VIEW_SOURCE_QUERY: {
clientType: 'mini_program',
clientRuntime: 'wechat_mini_program',
},
...configOverrides,
};
}
throw new Error(`Unexpected require: ${requestPath}`);
},
wx: wxMock,
'../host-bridge/wechatHostBridgeWebView': webViewBridge,
}) as {
createWechatWebViewPage: () => Record<string, unknown>;
};
vm.runInNewContext(source, sandbox, { filename: pageScriptPath });
if (!pageConfig) {
throw new Error('web-view page did not call Page()');
}
const pageConfig = shellModule.createWechatWebViewPage();
const page = {
...pageConfig,
@@ -94,6 +82,34 @@ function loadWebViewPage(
return page;
}
function loadCommonJsModule(
filePath: string,
requireMap: Record<string, unknown>,
) {
const source = readFileSync(filePath, 'utf8');
const module = { exports: {} as Record<string, unknown> };
const sandbox = {
console,
getCurrentPages: () => [],
module,
exports: module.exports,
setTimeout(callback: () => void) {
callback();
return 1;
},
require(requestPath: string) {
if (Object.prototype.hasOwnProperty.call(requireMap, requestPath)) {
return requireMap[requestPath];
}
throw new Error(`Unexpected require: ${requestPath}`);
},
wx: globalThis.wx,
};
vm.runInNewContext(source, sandbox, { filename: filePath });
return module.exports;
}
describe('mini-program web-view auth page', () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -264,6 +280,10 @@ describe('mini-program web-view auth page', () => {
data: {
token: 'jwt-pending-wechat',
bindingStatus: 'pending_bind_phone',
user: {
displayName: '陶泥玩家',
publicUserCode: 'SY-12345678',
},
},
});
});