fix(util): 修复内部页面绝对URL被误判为外链的问题
新增extractInternalAppPath和resolveNavMode工具函数,在diyRedirectTo中增加内部页面绝对URL还原逻辑: 1. 支持通过open_mode配置三种导航模式:auto/online/internal 2. 严格校验域名避免误伤外部站点,支持强制内部跳转场景 3. 修复H5整页刷新丢失状态、MP被塞进web-view的问题
This commit is contained in:
@@ -368,6 +368,114 @@ export function normalizeUrl(urlString) {
|
||||
return schemeAndHost + normalizedPath + normalizedQuery + hash;
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// extractInternalAppPath
|
||||
// =============================================================
|
||||
|
||||
/**
|
||||
* 从一个 URL 中提取"本 App 内部页面路径"。
|
||||
*
|
||||
* 背景 / 修复的 bug:
|
||||
* 后台装修数据里,内部页面常被存成【绝对 URL】,例如
|
||||
* https://host/hwappx/common/pages_tool/form/formdata?id=130
|
||||
* diyRedirectTo 用 `wap_url.indexOf('http') != -1` 判断"外部链接",于是:
|
||||
* - H5: 走 window.location.href → 整页刷新;点返回时整个 App 重启、启动屏重播、状态丢失
|
||||
* - MP: 走 web-view → 本应原生跳转的内部页被塞进 web-view
|
||||
* 本函数把这类"指向本 App 内部页面"的绝对 URL 还原成相对路径,
|
||||
* 让 diyRedirectTo 改走内部页面分支(uni.navigateTo / SPA 跳转)。
|
||||
*
|
||||
* 判定规则(双闸门,避免误伤真正的外部站点):
|
||||
* 闸门 1 —— host 校验(相对路径直接放行):
|
||||
* 绝对 URL 的 host 必须与本 App 自己的域名【完全一致】(忽略端口, 大小写不敏感);
|
||||
* 否则返回 null(视为真外链, 维持原逻辑)。
|
||||
* ★ 完全一致是最严格的口径: 不同子域(如 xcx3 vs xcx6)、不同域名一律当外链,
|
||||
* 彻底杜绝误伤 Shopify(xxx.myshopify.com/pages/about)、Facebook 等外站。
|
||||
* 闸门 2 —— 路径校验:
|
||||
* pathname 中出现以 App 页面根开头的段(pages / pages_xxx),
|
||||
* 则从该段起截取为内部路径(以 '/' 开头),连同 query/hash 一起返回;
|
||||
* 未命中返回 null。
|
||||
*
|
||||
* @example
|
||||
* const H = ['xcx6.aigc-quickapp.com']
|
||||
* extractInternalAppPath('https://xcx6.aigc-quickapp.com/hwappx/common/pages_tool/form/formdata?id=1', H)
|
||||
* // '/pages_tool/form/formdata?id=1' (域名完全一致 → 还原)
|
||||
* extractInternalAppPath('https://xcx3.aigc-quickapp.com/hwappx/common/pages/index/index', H)
|
||||
* // null (子域不同 → 维持外链)
|
||||
* extractInternalAppPath('https://xxx.myshopify.com/pages/about-us', H)
|
||||
* // null (外域名 → 维持外链,不误伤)
|
||||
* extractInternalAppPath('/pages_goods/detail?id=9', H)
|
||||
* // '/pages_goods/detail?id=9' (相对路径 → 内部)
|
||||
*
|
||||
* @param {string} url 绝对或相对 URL
|
||||
* @param {string|string[]|true} [allowedHosts] 本 App 自己的域名(可传完整 URL 或纯 host,会自动提取);
|
||||
* 传 `true` 表示【强制内部】—— 跳过 host 校验, 只要路径含 /pages 段就还原(用于 open_mode='internal')。
|
||||
* @returns {string|null} 形如 '/pages_tool/form/formdata?id=1' 的内部路径,或 null
|
||||
*/
|
||||
export function extractInternalAppPath(url, allowedHosts) {
|
||||
if (!url || typeof url !== 'string') return null;
|
||||
let rest = url;
|
||||
// 闸门 1: host 校验(完全一致);allowedHosts === true 时跳过(强制内部)
|
||||
const hostMatch = rest.match(/^https?:\/\/([^/]*)/i);
|
||||
if (hostMatch) {
|
||||
if (allowedHosts !== true) {
|
||||
const host = (hostMatch[1] || '').toLowerCase().replace(/:\d+$/, '');
|
||||
const allow = _normalizeHostList(allowedHosts);
|
||||
if (!host || allow.indexOf(host) === -1) return null; // 域名不完全一致 → 不改
|
||||
}
|
||||
rest = rest.substring(hostMatch[0].length);
|
||||
}
|
||||
// 闸门 2: 路径校验(找第一个"页面根段" /pages 或 /pages_xxx)
|
||||
const pageMatch = rest.match(/\/pages(?:_[a-z0-9]+)?\//i);
|
||||
if (!pageMatch) return null;
|
||||
const internal = rest.substring(pageMatch.index);
|
||||
return internal || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 allowedHosts 归一化成纯 host 数组(小写、去协议、去路径、去端口)。
|
||||
* 入参每一项可以是完整 URL(https://host/xx/)或纯 host(host:port)。
|
||||
* @param {string|string[]} hosts
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function _normalizeHostList(hosts) {
|
||||
if (!hosts) return [];
|
||||
const arr = Array.isArray(hosts) ? hosts : [hosts];
|
||||
const out = [];
|
||||
for (const h of arr) {
|
||||
if (!h || typeof h !== 'string') continue;
|
||||
let s = h.trim().toLowerCase();
|
||||
const m = s.match(/^https?:\/\/([^/]*)/i);
|
||||
if (m) s = m[1] || '';
|
||||
else s = s.replace(/\/.*$/, ''); // 去掉可能带的 path
|
||||
s = s.replace(/:\d+$/, ''); // 去端口
|
||||
if (s) out.push(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 link 的导航模式(统一开关)。用于 diyRedirectTo 决定内部页面绝对 URL 如何跳转。
|
||||
*
|
||||
* 取值来源(顶层字段 或 page_params 里, 大小写不敏感, 兼容驼峰 openMode):
|
||||
* - open_mode = 'internal' | 'spa' → 'internal' 强制走内部 SPA(忽略域名是否一致)
|
||||
* - open_mode = 'online' | 'web' → 'online' 强制走线上(H5 整页刷新 / MP web-view)
|
||||
* - 未设置 / 其他 → 'auto' 自动: 域名完全一致才内部, 否则线上
|
||||
*
|
||||
* @param {Object} link
|
||||
* @returns {'internal'|'online'|'auto'}
|
||||
*/
|
||||
export function resolveNavMode(link) {
|
||||
if (!link || typeof link !== 'object') return 'auto';
|
||||
const pp = (link.page_params && typeof link.page_params === 'object') ? link.page_params : {};
|
||||
const raw = link.open_mode != null ? link.open_mode
|
||||
: (link.openMode != null ? link.openMode
|
||||
: (pp.open_mode != null ? pp.open_mode : pp.openMode));
|
||||
const m = (typeof raw === 'string') ? raw.trim().toLowerCase() : '';
|
||||
if (m === 'internal' || m === 'spa') return 'internal';
|
||||
if (m === 'online' || m === 'web') return 'online';
|
||||
return 'auto';
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// appendQueryParams
|
||||
// =============================================================
|
||||
@@ -616,6 +724,7 @@ export function createNavigateApi(opts = {}) {
|
||||
cleanParamUrls,
|
||||
normalizeUrl,
|
||||
appendQueryParams,
|
||||
extractInternalAppPath,
|
||||
parsePageParams,
|
||||
resolveLinkUrl,
|
||||
decodeEntity,
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
cleanParamUrls,
|
||||
normalizeUrl as _normalizeUrlBase,
|
||||
appendQueryParams as _appendQueryParamsBase,
|
||||
extractInternalAppPath as _extractInternalAppPath,
|
||||
resolveNavMode as _resolveNavMode,
|
||||
_shouldEncodeUrl,
|
||||
_safeEncodeForPlatform,
|
||||
_navigateTo,
|
||||
@@ -1092,6 +1094,42 @@ export default {
|
||||
link.wap_url = resolvedUrl;
|
||||
}
|
||||
|
||||
// 修复: 内部页面被后台存成【绝对 URL】(如 https://host/hwappx/common/pages_tool/form/formdata)
|
||||
// 时,下方 `indexOf('http') != -1` 会把它误判为"外部链接":
|
||||
// - H5 走 window.location.href → 整页刷新,点返回时整个 App 重启、启动屏重播、状态/滚动丢失
|
||||
// - MP 走 web-view → 本应原生跳转的内部页被塞进 web-view
|
||||
// 这里把"指向本 App 内部页面"的绝对 URL 还原为相对路径,使其改走下方内部页面分支(SPA 跳转)。
|
||||
//
|
||||
// 由 open_mode 统一控制(顶层字段 或 page_params):
|
||||
// - 'auto'(默认): 双闸门 —— host 与本 App 域名【完全一致】且路径带 /pages 段才还原。
|
||||
// 不同子域/不同域名一律当外链,彻底避免误伤 Shopify、Facebook 等外站。
|
||||
// - 'internal': 强制内部 —— 跳过 host 校验, 只要路径带 /pages 段就还原为 SPA 跳转。
|
||||
// 用途: 数据里链接域名与当前部署域名不同(如 xcx3 vs xcx6)但确认是同一 App 内部页。
|
||||
// - 'online': 强制线上 —— 不还原, 保持整页刷新 / web-view。
|
||||
// 用途: 某些自域名页面是非 SPA / 遗留页,必须整页或 web-view 打开。
|
||||
if (link.wap_url && /^https?:\/\//i.test(link.wap_url)) {
|
||||
const navMode = _resolveNavMode(link); // 'internal' | 'online' | 'auto'
|
||||
if (navMode !== 'online') {
|
||||
let internalPath;
|
||||
if (navMode === 'internal') {
|
||||
// 强制内部: 传 true 跳过 host 校验
|
||||
internalPath = _extractInternalAppPath(link.wap_url, true);
|
||||
} else {
|
||||
// auto: host 完全一致才还原
|
||||
const appHosts = [];
|
||||
// #ifdef H5
|
||||
try { if (typeof location !== 'undefined' && location.hostname) appHosts.push(location.hostname); } catch (e) {}
|
||||
// #endif
|
||||
try { if (Config.h5Domain) appHosts.push(Config.h5Domain); } catch (e) {}
|
||||
try { if (Config.baseUrl) appHosts.push(Config.baseUrl); } catch (e) {}
|
||||
internalPath = _extractInternalAppPath(link.wap_url, appHosts);
|
||||
}
|
||||
if (internalPath) {
|
||||
link.wap_url = internalPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 外部链接
|
||||
if (link.wap_url && link.wap_url.indexOf('http') != -1) {
|
||||
// 公共参数 + 透传 page_params(resolveLinkUrl 已把 wap_url 中的 query 合并到 page_params)
|
||||
|
||||
@@ -37,7 +37,7 @@ function loadNavInSandbox() {
|
||||
.replace(/^export\s+let\s+/gm, 'let ')
|
||||
.replace(/^export\s+var\s+/gm, 'var ')
|
||||
+ '\nmodule.exports = { _toParamValueString, parsePageParams, resolveLinkUrl, '
|
||||
+ 'cleanParamUrls, normalizeUrl, appendQueryParams, getPlatform, shouldEncodeUrl, '
|
||||
+ 'cleanParamUrls, normalizeUrl, appendQueryParams, extractInternalAppPath, resolveNavMode, getPlatform, shouldEncodeUrl, '
|
||||
+ '_safeEncodeForPlatform, _navigateTo, createNavigateApi };\n';
|
||||
const sandbox = { console, globalThis: {}, module: moduleObj, exports: moduleObj.exports };
|
||||
sandbox.globalThis = sandbox;
|
||||
@@ -54,6 +54,7 @@ test('util-navigate.js: 能被 vm 加载,所有 export 都存在', () => {
|
||||
const expected = [
|
||||
'_toParamValueString', 'parsePageParams', 'resolveLinkUrl',
|
||||
'cleanParamUrls', 'normalizeUrl', 'appendQueryParams',
|
||||
'extractInternalAppPath', 'resolveNavMode',
|
||||
'getPlatform', 'shouldEncodeUrl',
|
||||
'_safeEncodeForPlatform', '_navigateTo', 'createNavigateApi',
|
||||
];
|
||||
@@ -341,17 +342,19 @@ test('util.js: 通过 import 引用 util-navigate 的方法,挂载到 default ex
|
||||
// 关键 import
|
||||
assert.ok(utilSrc.includes("from './util-navigate.js'"),
|
||||
'util.js 应 import util-navigate.js');
|
||||
assert.ok(utilSrc.includes('_cleanParamUrls'),
|
||||
'util.js 应 import _cleanParamUrls 别名');
|
||||
assert.ok(utilSrc.includes('_normalizeUrl'),
|
||||
'util.js 应 import _normalizeUrl 别名');
|
||||
assert.ok(utilSrc.includes('_navigateTo'),
|
||||
'util.js 应 import _navigateTo 别名');
|
||||
// default export 里的挂载
|
||||
assert.ok(utilSrc.includes('cleanParamUrls: (v) => _cleanParamUrls(v)'),
|
||||
assert.ok(utilSrc.includes('cleanParamUrls'),
|
||||
'util.js 应 import cleanParamUrls');
|
||||
assert.ok(utilSrc.includes('normalizeUrl as _normalizeUrlBase'),
|
||||
'util.js 应 import normalizeUrl 别名 _normalizeUrlBase');
|
||||
assert.ok(utilSrc.includes('extractInternalAppPath as _extractInternalAppPath'),
|
||||
'util.js 应 import extractInternalAppPath 别名');
|
||||
assert.ok(utilSrc.includes('resolveNavMode as _resolveNavMode'),
|
||||
'util.js 应 import resolveNavMode 别名');
|
||||
// default export 里的挂载(cleanParamUrls 直接挂, normalizeUrl 用包装函数委托)
|
||||
assert.ok(/cleanParamUrls,/.test(utilSrc),
|
||||
'util.js 应把 cleanParamUrls 挂载到 default export');
|
||||
assert.ok(utilSrc.includes('_navigateTo: (args) => _navigateTo(args)'),
|
||||
'util.js 应把 _navigateTo 挂载到 default export');
|
||||
assert.ok(utilSrc.includes('return _normalizeUrlBase(urlString)'),
|
||||
'util.js 的 normalizeUrl 应委托给 _normalizeUrlBase');
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
@@ -368,13 +371,152 @@ test('util.js: redirectTo / diyRedirectTo 仍在 util.js(没被搬走)', () => {
|
||||
// diyRedirectTo 函数定义
|
||||
assert.ok(/^\s*diyRedirectTo\s*\(\s*link\s*\)\s*\{/m.test(utilSrc),
|
||||
'diyRedirectTo 应仍在 util.js');
|
||||
// redirectTo 内部调 this._safeEncodeForPlatform
|
||||
assert.ok(utilSrc.includes('this._safeEncodeForPlatform'),
|
||||
'redirectTo 应调 this._safeEncodeForPlatform');
|
||||
// redirectTo 内部调 this._navigateTo
|
||||
assert.ok(utilSrc.includes('this._navigateTo('),
|
||||
'redirectTo 应调 this._navigateTo');
|
||||
// diyRedirectTo 内部调 this.cleanParamUrls
|
||||
// redirectTo 内部调 _safeEncodeForPlatform(模块函数直调)
|
||||
assert.ok(utilSrc.includes('_safeEncodeForPlatform('),
|
||||
'redirectTo 应调 _safeEncodeForPlatform');
|
||||
// diyRedirectTo 内部调 this.cleanParamUrls 清理脏 link
|
||||
assert.ok(utilSrc.includes('this.cleanParamUrls('),
|
||||
'diyRedirectTo 应调 this.cleanParamUrls');
|
||||
// diyRedirectTo 内部调 _resolveNavMode + _extractInternalAppPath(内部页绝对 URL 还原)
|
||||
assert.ok(utilSrc.includes('_resolveNavMode('),
|
||||
'diyRedirectTo 应调 _resolveNavMode');
|
||||
assert.ok(utilSrc.includes('_extractInternalAppPath('),
|
||||
'diyRedirectTo 应调 _extractInternalAppPath');
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// extractInternalAppPath — 内部页面绝对 URL 还原(双闸门)
|
||||
// 修复: 内部页被存成绝对 URL 时被误判外链 → 整页刷新 / web-view
|
||||
// =============================================================
|
||||
test('extractInternalAppPath: 相对内部路径直接放行', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
assert.equal(nav.extractInternalAppPath('/pages_goods/detail?id=9'), '/pages_goods/detail?id=9');
|
||||
assert.equal(nav.extractInternalAppPath('/pages/index/index'), '/pages/index/index');
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 同域绝对 URL → 还原为相对路径', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['xcx6.aigc-quickapp.com'];
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://xcx6.aigc-quickapp.com/hwappx/common/pages_tool/form/formdata?id=1', H),
|
||||
'/pages_tool/form/formdata?id=1',
|
||||
);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 同平台不同子域(域名不完全一致)→ null(严格模式)', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['xcx6.aigc-quickapp.com']; // App 跑在 xcx6, 数据链接是 xcx3 → 视为外链
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://xcx3.aigc-quickapp.com/hwappx/common/pages_tool/form/formdata', H),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 传入完整 URL 作为白名单也能提取 host(完全一致才还原)', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['https://xcx6.aigc-quickapp.com/']; // Config.h5Domain 形式
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://xcx6.aigc-quickapp.com/hwappx/common/pages/index/index', H),
|
||||
'/pages/index/index',
|
||||
);
|
||||
// host 不一致 → null
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://xcx3.aigc-quickapp.com/hwappx/common/pages/index/index', H),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 外链域名不同(Shopify / Facebook)→ 不误伤, 返回 null', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['xcx6.aigc-quickapp.com'];
|
||||
assert.equal(nav.extractInternalAppPath('https://xxx.myshopify.com/pages/about-us', H), null);
|
||||
assert.equal(nav.extractInternalAppPath('https://www.facebook.com/pages/Name/123', H), null);
|
||||
assert.equal(nav.extractInternalAppPath('https://example.com/pages_tool/x', H), null);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 绝对 URL 但路径无 /pages 段 → null(真外链)', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['xcx6.aigc-quickapp.com'];
|
||||
assert.equal(nav.extractInternalAppPath('https://xcx6.aigc-quickapp.com/activity/2026', H), null);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 无白名单时绝对 URL 一律 null(不猜测)', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
assert.equal(nav.extractInternalAppPath('https://xcx3.aigc-quickapp.com/hwappx/common/pages/index/index'), null);
|
||||
assert.equal(nav.extractInternalAppPath('https://xcx3.aigc-quickapp.com/hwappx/common/pages/index/index', []), null);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 空/非法输入安全返回 null', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
assert.equal(nav.extractInternalAppPath('', ['a.com']), null);
|
||||
assert.equal(nav.extractInternalAppPath(null, ['a.com']), null);
|
||||
assert.equal(nav.extractInternalAppPath(undefined), null);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: host 大小写不敏感 + 忽略端口', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['xcx6.aigc-quickapp.com'];
|
||||
// host 大写
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://XCX6.AIGC-QUICKAPP.COM/hwappx/common/pages/index/index', H),
|
||||
'/pages/index/index',
|
||||
);
|
||||
// 带端口(白名单不带端口, 也应命中)
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://xcx6.aigc-quickapp.com:8443/hwappx/common/pages_goods/detail', H),
|
||||
'/pages_goods/detail',
|
||||
);
|
||||
});
|
||||
|
||||
test('extractInternalAppPath: 各分包页面根都能识别 + 保留 hash', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
const H = ['h.com'];
|
||||
assert.equal(nav.extractInternalAppPath('https://h.com/base/pages_promotion/point/list', H), '/pages_promotion/point/list');
|
||||
assert.equal(nav.extractInternalAppPath('https://h.com/base/pages_order/detail?id=2', H), '/pages_order/detail?id=2');
|
||||
assert.equal(nav.extractInternalAppPath('/pages/index/index#top'), '/pages/index/index#top');
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// extractInternalAppPath: 强制内部(allowedHosts === true 跳过 host 校验)
|
||||
// =============================================================
|
||||
test('extractInternalAppPath: allowedHosts=true → 跳过 host 校验(强制内部)', () => {
|
||||
const nav = loadNavInSandbox();
|
||||
// 不同子域, 正常严格模式下会 null; 传 true 强制还原
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://xcx3.aigc-quickapp.com/hwappx/common/pages_tool/form/formdata?id=1', true),
|
||||
'/pages_tool/form/formdata?id=1',
|
||||
);
|
||||
// 完全陌生域名也能被强制还原(调用方已明确表示是内部页)
|
||||
assert.equal(
|
||||
nav.extractInternalAppPath('https://any-host.example.net/x/pages/index/index', true),
|
||||
'/pages/index/index',
|
||||
);
|
||||
// 但仍要求路径带 /pages 段, 没有则 null
|
||||
assert.equal(nav.extractInternalAppPath('https://any-host.example.net/activity/2026', true), null);
|
||||
});
|
||||
|
||||
// =============================================================
|
||||
// resolveNavMode — 统一开关 open_mode(internal/online/auto)
|
||||
// =============================================================
|
||||
test('resolveNavMode: open_mode=internal/spa → internal', () => {
|
||||
const { resolveNavMode } = loadNavInSandbox();
|
||||
assert.equal(resolveNavMode({ open_mode: 'internal' }), 'internal');
|
||||
assert.equal(resolveNavMode({ open_mode: 'SPA' }), 'internal');
|
||||
assert.equal(resolveNavMode({ openMode: 'internal' }), 'internal');
|
||||
assert.equal(resolveNavMode({ page_params: { open_mode: 'internal' } }), 'internal');
|
||||
});
|
||||
|
||||
test('resolveNavMode: open_mode=online/web → online', () => {
|
||||
const { resolveNavMode } = loadNavInSandbox();
|
||||
assert.equal(resolveNavMode({ open_mode: 'online' }), 'online');
|
||||
assert.equal(resolveNavMode({ open_mode: 'WEB' }), 'online');
|
||||
assert.equal(resolveNavMode({ page_params: { open_mode: 'web' } }), 'online');
|
||||
});
|
||||
|
||||
test('resolveNavMode: 未设置 / 未知值 → auto', () => {
|
||||
const { resolveNavMode } = loadNavInSandbox();
|
||||
assert.equal(resolveNavMode({}), 'auto');
|
||||
assert.equal(resolveNavMode({ open_mode: 'xxx' }), 'auto');
|
||||
assert.equal(resolveNavMode(null), 'auto');
|
||||
assert.equal(resolveNavMode(undefined), 'auto');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user