import { describe, expect, test } from 'vitest'; import { HOST_BRIDGE_CAPABILITIES, HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS, HOST_BRIDGE_DESKTOP_NETWORK_CHECK_TIMEOUT_MS, HOST_BRIDGE_DOCUMENT_MIME_TYPES, HOST_BRIDGE_EVENTS, HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES, HOST_BRIDGE_IMPORT_DOCUMENT_MAX_BYTES, HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT, HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS, HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID, HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS, HOST_BRIDGE_NATIVE_APP_QUERY, HOST_BRIDGE_NATIVE_APP_QUERY_KEY, HOST_BRIDGE_NATIVE_APP_QUERY_KEYS, HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS, HOST_BRIDGE_PUBLIC_WEB_ORIGIN, HOST_BRIDGE_PUBLIC_WEB_URL, HOST_BRIDGE_RESPONSE_CACHE_MAX, HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY, HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS, HOST_BRIDGE_SCANNER_TIMEOUT_MS, HOST_BRIDGE_TAURI_COMMAND, HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES, HOST_BRIDGE_TEXT_MIME_TYPES, HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS, HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES, HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY, isHostBridgeCapability, isHostBridgeEventName, isHostBridgeMethod, normalizeHostBridgeAppTitle, normalizeHostBridgeBadgeCount, normalizeHostBridgeClipboardText, normalizeHostBridgeColorScheme, normalizeHostBridgeConnectionType, normalizeHostBridgeExportAudioPayload, normalizeHostBridgeExportFileName, normalizeHostBridgeExportImagePayload, normalizeHostBridgeExportTextPayload, normalizeHostBridgeExternalUrl, normalizeHostBridgeExternalUrlPayload, normalizeHostBridgeHapticsImpactStyle, normalizeHostBridgeImportAudioResult, normalizeHostBridgeImportDocumentResult, normalizeHostBridgeImportFileName, normalizeHostBridgeImportImageResult, normalizeHostBridgeImportTextResult, normalizeHostBridgeLifecycleState, normalizeHostBridgeLocalNotification, normalizeHostBridgeQrCodeValue, normalizeHostBridgeRequestId, normalizeHostBridgeShareOpenPayload, } from './hostBridge'; describe('HostBridge shared contract helpers', () => { test('固定公开 H5 主站入口', () => { expect(HOST_BRIDGE_PUBLIC_WEB_ORIGIN).toBe('https://www.genarrative.world'); expect(HOST_BRIDGE_PUBLIC_WEB_URL).toBe('https://www.genarrative.world/'); }); test('固定 Tauri 只暴露唯一 HostBridge command', () => { expect(HOST_BRIDGE_TAURI_COMMAND).toBe('host_bridge_request'); }); test('固定移动壳 WebView 下载协议阻断清单', () => { expect(HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS).toEqual([ 'blob:', 'data:', 'file:', 'filesystem:', ]); }); test('固定移动壳本地通知频道', () => { expect(HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID).toBe( 'genarrative-local', ); }); test('固定原生壳请求超时边界', () => { expect(HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS).toBe(8000); expect(HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS).toBe(60000); expect(HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS).toBe(3000); expect(HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS).toBe(30000); expect(HOST_BRIDGE_SCANNER_TIMEOUT_MS).toBe(60000); expect(HOST_BRIDGE_DESKTOP_NETWORK_CHECK_TIMEOUT_MS).toBe(1200); }); test('固定宿主侧响应回放缓存边界', () => { expect(HOST_BRIDGE_RESPONSE_CACHE_MAX).toBe(128); }); test('固定宿主上下文 query 契约', () => { expect(HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY).toEqual({ clientRuntime: 'clientRuntime', clientType: 'clientType', miniProgramEnv: 'miniProgramEnv', hostShell: 'hostShell', hostPlatform: 'hostPlatform', hostVersion: 'hostVersion', bridgeVersion: 'bridgeVersion', hostCapabilities: 'hostCapabilities', }); expect(HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS).toEqual([ 'clientType', 'clientRuntime', 'miniProgramEnv', 'hostShell', 'hostPlatform', 'hostVersion', 'bridgeVersion', 'hostCapabilities', ]); expect(HOST_BRIDGE_NATIVE_APP_QUERY_KEY).toEqual({ clientRuntime: 'clientRuntime', clientType: 'clientType', hostShell: 'hostShell', hostPlatform: 'hostPlatform', hostVersion: 'hostVersion', bridgeVersion: 'bridgeVersion', hostCapabilities: 'hostCapabilities', }); expect(HOST_BRIDGE_NATIVE_APP_QUERY_KEYS).toEqual([ 'clientRuntime', 'clientType', 'hostShell', 'hostPlatform', 'hostVersion', 'bridgeVersion', 'hostCapabilities', ]); expect(HOST_BRIDGE_NATIVE_APP_QUERY).toEqual({ clientRuntime: 'native_app', clientType: 'native_app', hostShellExpoMobile: 'expo_mobile', hostShellTauriDesktop: 'tauri_desktop', }); expect(HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY).toEqual({ clientType: 'mini_program', clientRuntime: 'wechat_mini_program', }); }); 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(isHostBridgeMethod('host.getRuntime')).toBe(true); expect(isHostBridgeMethod('share.open')).toBe(true); expect(isHostBridgeMethod('app.lifecycle')).toBe(false); expect(isHostBridgeMethod('unknown.method')).toBe(false); expect(isHostBridgeCapability('appearance.getColorScheme')).toBe(true); expect(isHostBridgeCapability('share.open')).toBe(true); expect(isHostBridgeCapability('app.reloadWebView')).toBe(true); expect(isHostBridgeCapability('app.lifecycle')).toBe(true); expect(isHostBridgeCapability('network.status')).toBe(true); expect(isHostBridgeCapability('network.statusChanged')).toBe(true); expect(isHostBridgeCapability('clipboard.readText')).toBe(true); expect(isHostBridgeCapability('file.importText')).toBe(true); expect(isHostBridgeCapability('file.importDocument')).toBe(true); expect(isHostBridgeCapability('file.importImage')).toBe(true); expect(isHostBridgeCapability('file.captureImage')).toBe(true); expect(isHostBridgeCapability('scanner.scanQrCode')).toBe(true); expect(isHostBridgeCapability('file.importAudio')).toBe(true); expect(isHostBridgeCapability('file.exportAudio')).toBe(true); expect(isHostBridgeCapability('file.imageDropped')).toBe(true); expect(isHostBridgeCapability('app.setBadgeCount')).toBe(true); expect(isHostBridgeCapability('notification.showLocal')).toBe(true); expect(isHostBridgeCapability('navigation.canGoBack')).toBe(true); expect(isHostBridgeCapability('unknown.capability')).toBe(false); expect(isHostBridgeCapability(null)).toBe(false); }); test('识别 HostBridge 事件白名单', () => { expect(HOST_BRIDGE_EVENTS).toEqual([ 'app.lifecycle', 'network.statusChanged', 'navigation.canGoBack', 'file.imageDropped', ]); for (const eventName of HOST_BRIDGE_EVENTS) { expect(isHostBridgeEventName(eventName)).toBe(true); expect(isHostBridgeCapability(eventName)).toBe(true); expect(isHostBridgeMethod(eventName)).toBe(false); } expect(isHostBridgeEventName('host.getRuntime')).toBe(false); expect(isHostBridgeEventName('unknown.event')).toBe(false); expect(isHostBridgeEventName(null)).toBe(false); }); test('宿主壳 capability profile 来自共享白名单', () => { const profiles = [ HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES, HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES, ]; for (const profile of profiles) { expect( profile.every((capability) => HOST_BRIDGE_CAPABILITIES.includes(capability), ), ).toBe(true); } expect(HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES).toEqual([ 'auth.requestLogin', 'payment.request', 'share.setTarget', 'share.open', ]); for (const nativeAppProfile of [ HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES, HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES, ]) { expect(nativeAppProfile).not.toContain('auth.requestLogin'); expect(nativeAppProfile).not.toContain('payment.request'); } }); test('移动和桌面 profile 保留真实平台差异', () => { expect(HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES).toEqual([ 'app.setBadgeCount', ]); expect(HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES).toEqual([ ...HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES, ...HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES, ]); expect(HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES).toContain( 'file.captureImage', ); expect(HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES).toContain( 'file.importDocument', ); expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).toContain( 'file.importDocument', ); expect(HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES).toContain( 'scanner.scanQrCode', ); expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).toContain('app.setTitle'); expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).toContain( 'file.imageDropped', ); expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).not.toContain( 'file.captureImage', ); expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).not.toContain( 'scanner.scanQrCode', ); expect(HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES).not.toContain( 'haptics.impact', ); }); test('归一化 HostBridge request id', () => { expect(normalizeHostBridgeRequestId(' request-1 ')).toBe('request-1'); expect(normalizeHostBridgeRequestId('')).toBeNull(); expect(normalizeHostBridgeRequestId('request\n1')).toBeNull(); expect(normalizeHostBridgeRequestId('a'.repeat(121))).toBeNull(); expect(normalizeHostBridgeRequestId(null)).toBeNull(); }); test('归一化宿主外链打开载荷', () => { expect( normalizeHostBridgeExternalUrlPayload(' https://example.com/path '), ).toEqual({ url: 'https://example.com/path', }); expect( normalizeHostBridgeExternalUrlPayload('mailto:hi@example.com'), ).toEqual({ url: 'mailto:hi@example.com', }); expect(normalizeHostBridgeExternalUrlPayload('')).toBeNull(); expect(normalizeHostBridgeExternalUrlPayload('bad\nurl')).toBeNull(); expect( normalizeHostBridgeExternalUrlPayload('javascript:alert(1)'), ).toBeNull(); expect(normalizeHostBridgeExternalUrlPayload(null)).toBeNull(); }); test('归一化宿主剪贴板读取文本', () => { expect(normalizeHostBridgeClipboardText('作品号 PZ-1')).toEqual({ text: '作品号 PZ-1', }); expect(normalizeHostBridgeClipboardText('a'.repeat(100010))).toEqual({ text: 'a'.repeat(100000), }); expect(normalizeHostBridgeClipboardText(null)).toBeNull(); }); test('归一化宿主窗口标题', () => { expect(normalizeHostBridgeAppTitle(' 拼图 - 陶泥儿 ')).toEqual({ title: '拼图 - 陶泥儿', }); expect(normalizeHostBridgeAppTitle('')).toBeNull(); expect(normalizeHostBridgeAppTitle('拼图\n陶泥儿')).toBeNull(); expect(normalizeHostBridgeAppTitle('a'.repeat(90))).toEqual({ title: 'a'.repeat(80), }); }); test('归一化宿主二维码扫码结果', () => { expect(normalizeHostBridgeQrCodeValue(' https://example.com/a ')).toEqual( { value: 'https://example.com/a', format: 'qr_code', }, ); expect(normalizeHostBridgeQrCodeValue('')).toBeNull(); expect(normalizeHostBridgeQrCodeValue('bad\nvalue')).toBeNull(); expect(normalizeHostBridgeQrCodeValue(null)).toBeNull(); expect(normalizeHostBridgeQrCodeValue('a'.repeat(4100))).toEqual({ value: 'a'.repeat(4096), format: 'qr_code', }); }); test('文档导入契约包含文本和 DOCX 边界', () => { expect(HOST_BRIDGE_DOCUMENT_MIME_TYPES).toEqual([ ...HOST_BRIDGE_TEXT_MIME_TYPES, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ]); expect(HOST_BRIDGE_IMPORT_DOCUMENT_MAX_BYTES).toBe(5 * 1024 * 1024); }); test('归一化宿主文本、图片和音频导出载荷', () => { expect( normalizeHostBridgeExportTextPayload({ fileName: ' ../作品:记录?.md ', content: 'content', mimeType: 'text/markdown', }), ).toEqual({ fileName: '作品-记录-.md', content: 'content', mimeType: 'text/markdown', }); expect( normalizeHostBridgeExportTextPayload({ fileName: '作品记录.txt', content: 'content', }), ).toEqual({ fileName: '作品记录.txt', content: 'content', }); expect( normalizeHostBridgeExportImagePayload({ fileName: ' ../分享:卡?.png ', base64Data: ' c2hhcmU= ', mimeType: 'image/png', }), ).toEqual({ fileName: '分享-卡-.png', base64Data: 'c2hhcmU=', mimeType: 'image/png', }); expect( normalizeHostBridgeExportAudioPayload({ fileName: '敲击音效.wav', base64Data: ' YXVkaW8= ', mimeType: 'audio/wav', }), ).toEqual({ fileName: '敲击音效.wav', base64Data: 'YXVkaW8=', mimeType: 'audio/wav', }); expect( normalizeHostBridgeExportTextPayload({ fileName: 'bad.bin', content: 'content', mimeType: 'application/octet-stream', }), ).toBeNull(); expect( normalizeHostBridgeExportImagePayload({ fileName: 'bad.gif', base64Data: 'c2hhcmU=', mimeType: 'image/gif', }), ).toBeNull(); expect( normalizeHostBridgeExportAudioPayload({ fileName: 'empty.wav', base64Data: ' ', mimeType: 'audio/wav', }), ).toBeNull(); }); test('归一化宿主文件导入结果', () => { expect(normalizeHostBridgeImportFileName(' ../剧情:草稿?.md ')).toBe( '剧情-草稿-.md', ); expect( normalizeHostBridgeImportTextResult({ action: 'selected', fileName: ' ../剧情:草稿?.md ', content: '暖灯猫街', mimeType: 'text/markdown', bytes: 12, }), ).toEqual({ action: 'selected', fileName: '剧情-草稿-.md', content: '暖灯猫街', mimeType: 'text/markdown', bytes: 12, }); expect( normalizeHostBridgeImportDocumentResult({ action: 'selected', fileName: ' 世界设定.docx ', base64Data: ' UEsDBGRvY3g= ', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', bytes: 8, }), ).toEqual({ action: 'selected', fileName: '世界设定.docx', base64Data: 'UEsDBGRvY3g=', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', bytes: 8, }); expect( normalizeHostBridgeImportImageResult({ action: 'dropped', fileName: ' 参考图.png ', base64Data: ' aW1hZ2U= ', mimeType: 'image/png', bytes: 5, position: { x: 12, y: 24 }, }), ).toEqual({ action: 'dropped', fileName: '参考图.png', base64Data: 'aW1hZ2U=', mimeType: 'image/png', bytes: 5, position: { x: 12, y: 24 }, }); expect( normalizeHostBridgeImportAudioResult({ action: 'selected', fileName: ' 敲击音效.webm ', base64Data: ' YXVkaW8= ', mimeType: 'audio/webm', bytes: 5, }), ).toEqual({ action: 'selected', fileName: '敲击音效.webm', base64Data: 'YXVkaW8=', mimeType: 'audio/webm', bytes: 5, }); expect( normalizeHostBridgeImportTextResult({ action: 'selected', fileName: 'bad.bin', content: 'content', mimeType: 'application/octet-stream', bytes: 7, }), ).toBeNull(); expect( normalizeHostBridgeImportAudioResult({ action: 'selected', fileName: 'huge.webm', base64Data: 'YXVkaW8=', mimeType: 'audio/webm', bytes: 20 * 1024 * 1024 + 1, }), ).toBeNull(); }); test('归一化宿主本地通知内容', () => { expect(HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT).toEqual({ action: 'delivered_to_system', }); expect( normalizeHostBridgeLocalNotification({ title: ' 生成完成 ', body: ' 作品已准备好 可以试玩 ', }), ).toEqual({ title: '生成完成', body: '作品已准备好 可以试玩', }); expect( normalizeHostBridgeLocalNotification({ title: '生成完成', body: '', }), ).toEqual({ title: '生成完成', }); expect( normalizeHostBridgeLocalNotification({ title: 'a'.repeat(90), body: 'b'.repeat(250), }), ).toEqual({ title: 'a'.repeat(80), body: 'b'.repeat(240), }); expect(normalizeHostBridgeLocalNotification({ title: '' })).toBeNull(); expect( normalizeHostBridgeLocalNotification({ title: '生成\n完成' }), ).toBeNull(); expect( normalizeHostBridgeLocalNotification({ title: '生成完成', body: '坏\u0001内容', }), ).toBeNull(); expect(normalizeHostBridgeLocalNotification(null)).toBeNull(); }); test('归一化宿主分享载荷', () => { expect( normalizeHostBridgeShareOpenPayload({ title: ' 暖灯猫街 ', message: ' 来玩 ', url: '/works/detail?work=PZ-1', }), ).toEqual({ status: 'valid', payload: { title: '暖灯猫街', message: '来玩', url: 'https://www.genarrative.world/works/detail?work=PZ-1', }, }); expect( normalizeHostBridgeShareOpenPayload({ title: '作品', work: 'PZ 1/二', }), ).toEqual({ status: 'valid', payload: { title: '作品', url: 'https://www.genarrative.world/works/detail?work=PZ+1%2F%E4%BA%8C', }, }); expect( normalizeHostBridgeShareOpenPayload({ title: '作品', targetPath: '//www.genarrative.world/works/detail?work=PZ-1', }), ).toEqual({ status: 'invalid' }); expect( normalizeHostBridgeShareOpenPayload({ title: '作品', url: 'https://example.com/works/detail?work=PZ-1', }), ).toEqual({ status: 'invalid' }); expect(normalizeHostBridgeShareOpenPayload({})).toEqual({ status: 'empty', }); expect(normalizeHostBridgeShareOpenPayload(null)).toEqual({ status: 'empty', }); }); test('归一化宿主触觉反馈强度', () => { expect(normalizeHostBridgeHapticsImpactStyle(undefined)).toBe('light'); expect(normalizeHostBridgeHapticsImpactStyle('light')).toBe('light'); expect(normalizeHostBridgeHapticsImpactStyle('medium')).toBe('medium'); expect(normalizeHostBridgeHapticsImpactStyle('heavy')).toBe('heavy'); expect(normalizeHostBridgeHapticsImpactStyle('rigid')).toBeNull(); expect(normalizeHostBridgeHapticsImpactStyle(null)).toBeNull(); }); 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'); }); test('归一化宿主网络连接类型', () => { expect(normalizeHostBridgeConnectionType('WIFI')).toBe('wifi'); expect(normalizeHostBridgeConnectionType('CELLULAR')).toBe('cellular'); expect(normalizeHostBridgeConnectionType('ETHERNET')).toBe('ethernet'); expect(normalizeHostBridgeConnectionType('NONE')).toBe('none'); expect(normalizeHostBridgeConnectionType('satellite')).toBe('unknown'); expect(normalizeHostBridgeConnectionType(null)).toBe('unknown'); }); });