新增 app.lifecycle HostBridge 能力与 H5 订阅入口 Expo 壳通过 React Native AppState 注入真实前后台状态 Tauri 壳通过主窗口 focus 和 blur 注入真实激活状态 更新壳能力漂移检查、测试和架构文档
84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
|
|
|
import {
|
|
isHostBridgeCapability,
|
|
normalizeHostBridgeBadgeCount,
|
|
normalizeHostBridgeColorScheme,
|
|
normalizeHostBridgeExportFileName,
|
|
normalizeHostBridgeExternalUrl,
|
|
normalizeHostBridgeLifecycleState,
|
|
} from './hostBridge';
|
|
|
|
describe('HostBridge shared contract helpers', () => {
|
|
test('只允许明确的外链协议交给宿主打开', () => {
|
|
expect(normalizeHostBridgeExternalUrl(' https://example.com/a ')).toBe(
|
|
'https://example.com/a',
|
|
);
|
|
expect(normalizeHostBridgeExternalUrl('mailto:hi@example.com')).toBe(
|
|
'mailto:hi@example.com',
|
|
);
|
|
expect(normalizeHostBridgeExternalUrl('tel:+12345678')).toBe(
|
|
'tel:+12345678',
|
|
);
|
|
});
|
|
|
|
test('拒绝空值、控制字符和危险协议', () => {
|
|
expect(normalizeHostBridgeExternalUrl('')).toBeNull();
|
|
expect(normalizeHostBridgeExternalUrl('javascript:alert(1)')).toBeNull();
|
|
expect(normalizeHostBridgeExternalUrl('file:///etc/passwd')).toBeNull();
|
|
expect(
|
|
normalizeHostBridgeExternalUrl('https://example.com/\nnext'),
|
|
).toBeNull();
|
|
expect(normalizeHostBridgeExternalUrl('/relative/path')).toBeNull();
|
|
});
|
|
|
|
test('归一化宿主导出文件名', () => {
|
|
expect(normalizeHostBridgeExportFileName(' 作品:记录?.txt ')).toBe(
|
|
'作品-记录-.txt',
|
|
);
|
|
expect(normalizeHostBridgeExportFileName('../secret.txt')).toBe(
|
|
'secret.txt',
|
|
);
|
|
expect(normalizeHostBridgeExportFileName('')).toBe(
|
|
'genarrative-export.txt',
|
|
);
|
|
expect(normalizeHostBridgeExportFileName('a'.repeat(140))).toHaveLength(
|
|
120,
|
|
);
|
|
});
|
|
|
|
test('识别 HostBridge 能力白名单', () => {
|
|
expect(isHostBridgeCapability('appearance.getColorScheme')).toBe(true);
|
|
expect(isHostBridgeCapability('share.open')).toBe(true);
|
|
expect(isHostBridgeCapability('app.lifecycle')).toBe(true);
|
|
expect(isHostBridgeCapability('app.setBadgeCount')).toBe(true);
|
|
expect(isHostBridgeCapability('navigation.canGoBack')).toBe(true);
|
|
expect(isHostBridgeCapability('unknown.capability')).toBe(false);
|
|
expect(isHostBridgeCapability(null)).toBe(false);
|
|
});
|
|
|
|
test('归一化宿主角标数量', () => {
|
|
expect(normalizeHostBridgeBadgeCount(0)).toBe(0);
|
|
expect(normalizeHostBridgeBadgeCount(12)).toBe(12);
|
|
expect(normalizeHostBridgeBadgeCount(99999)).toBe(99999);
|
|
expect(normalizeHostBridgeBadgeCount(-1)).toBeNull();
|
|
expect(normalizeHostBridgeBadgeCount(1.5)).toBeNull();
|
|
expect(normalizeHostBridgeBadgeCount(100000)).toBeNull();
|
|
expect(normalizeHostBridgeBadgeCount('1')).toBeNull();
|
|
});
|
|
|
|
test('归一化宿主配色模式', () => {
|
|
expect(normalizeHostBridgeColorScheme('light')).toBe('light');
|
|
expect(normalizeHostBridgeColorScheme('dark')).toBe('dark');
|
|
expect(normalizeHostBridgeColorScheme('unspecified')).toBe('unknown');
|
|
expect(normalizeHostBridgeColorScheme(null)).toBe('unknown');
|
|
});
|
|
|
|
test('归一化宿主生命周期状态', () => {
|
|
expect(normalizeHostBridgeLifecycleState('active')).toBe('active');
|
|
expect(normalizeHostBridgeLifecycleState('background')).toBe('background');
|
|
expect(normalizeHostBridgeLifecycleState('extension')).toBe('inactive');
|
|
expect(normalizeHostBridgeLifecycleState(null)).toBe('inactive');
|
|
});
|
|
});
|