platform.test.mjs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { desktopPlatform, desktopMenu, loginItemOptions, clientOnlyRuntime } from './platform.mjs'
  4. import { DesktopOnboarding } from './onboarding.mjs'
  5. import { hostPathInput, pathInside } from '../src/shared/hostPaths.mjs'
  6. test('Windows can only connect to a server, including direct onboarding calls', () => {
  7. const platform = desktopPlatform('win32')
  8. assert.deepEqual(platform.supportedModes, ['client'])
  9. const onboarding = new DesktopOnboarding({ ...platform, remoteServer: () => '' })
  10. assert.equal(onboarding.snapshot().mode, 'remote')
  11. assert.equal(onboarding.snapshot().platform, 'win32')
  12. assert.throws(() => onboarding.select('local'), /不支持/)
  13. assert.throws(() => onboarding.open({ mode: 'local' }), /不支持/)
  14. assert.equal(onboarding.select('remote').mode, 'remote')
  15. assert.deepEqual(desktopPlatform('darwin').supportedModes, ['client', 'server'])
  16. })
  17. test('a client-only macOS build follows the Windows client policy', () => {
  18. const platform = desktopPlatform('darwin', { clientOnly: true })
  19. assert.equal(platform.server, false)
  20. assert.deepEqual(platform.supportedModes, ['client'])
  21. assert.equal(platform.computer, true, 'remote computer control stays available')
  22. const onboarding = new DesktopOnboarding({ ...platform, remoteServer: () => '' })
  23. assert.equal(onboarding.snapshot().mode, 'remote')
  24. assert.throws(() => onboarding.select('local'), /不支持/)
  25. assert.equal(onboarding.select('remote').mode, 'remote')
  26. assert.deepEqual(desktopPlatform('darwin').supportedModes, ['client', 'server'], 'default darwin policy is untouched')
  27. })
  28. test('client-only macOS menus omit local service actions but keep remote login and quit', () => {
  29. const template = [{ label: '夭夭', submenu: [
  30. { role: 'about', label: '关于夭夭' }, { type: 'separator' },
  31. { id: 'desktop-service-status', label: '正在连接后台服务…', enabled: false },
  32. { id: 'desktop-local-vm', label: '本地虚拟机设置…' },
  33. { id: 'desktop-background', label: '登录启动时仅驻留菜单栏', type: 'checkbox' },
  34. { id: 'desktop-login', label: '登录 macOS 时启动', type: 'checkbox' },
  35. { id: 'desktop-quit', label: '退出夭夭(后台继续运行)' },
  36. { id: 'desktop-stop-and-quit', label: '停止后台服务并退出' }] },
  37. { label: '执行节点', submenu: [{ id: 'runner-import' }] },
  38. { label: '运行模式', submenu: [{ id: 'desktop-host-use-local' }, { id: 'desktop-host-ask' }, { label: '更换服务器…' }] },
  39. { label: '电脑', submenu: [{ id: 'desktop-host-authorize' }] },
  40. { role: 'editMenu', label: '编辑' }]
  41. const menus = desktopMenu(template, 'darwin', { clientOnly: true })
  42. assert.deepEqual(menus.map(menu => menu.label), ['夭夭', '运行模式', '电脑', '编辑'])
  43. const labels = menus[0].submenu.filter(item => item.type !== 'separator').map(item => item.label)
  44. assert.deepEqual(labels, ['关于夭夭', '尚未连接服务器', '登录启动时仅驻留菜单栏', '登录 Mac 时启动', '退出夭夭'])
  45. assert.deepEqual(menus[1].submenu, [{ label: '更换服务器…' }])
  46. assert.deepEqual(menus[2].submenu, [{ id: 'desktop-host-authorize' }])
  47. })
  48. test('the client-only marker is detected from the runtime root or environment override', () => {
  49. assert.equal(clientOnlyRuntime('/nonexistent-runtime-path'), false)
  50. process.env.HERMES_YAOYAO_DESKTOP_CLIENT_ONLY = '1'
  51. try {
  52. assert.equal(clientOnlyRuntime('/nonexistent-runtime-path'), true)
  53. assert.equal(desktopPlatform('darwin', {}).server, true, 'explicit options still decide the policy')
  54. } finally { delete process.env.HERMES_YAOYAO_DESKTOP_CLIENT_ONLY }
  55. })
  56. test('Windows menus retain useful actions and omit all local service actions', () => {
  57. const template = [{ label: '夭夭', submenu: [{ role: 'hide' }, { id: 'desktop-local-vm' }, { id: 'desktop-login' }, { id: 'desktop-quit', label: '退出夭夭(后台继续运行)' }] },
  58. { label: '执行节点', submenu: [{ id: 'runner-import' }] }, { label: '运行模式', submenu: [{ id: 'desktop-host-use-local' }, { label: '更换服务器…' }] }]
  59. const windows = desktopMenu(template, 'win32')
  60. assert.equal(windows.length, 2)
  61. assert.deepEqual(windows[0].submenu.map(item => item.label), ['登录 Windows 时启动', '退出夭夭'])
  62. assert.deepEqual(windows[1].submenu, [{ label: '更换服务器…' }])
  63. assert.equal(desktopMenu(template, 'darwin'), template)
  64. assert.deepEqual(loginItemOptions('win32', 'C:\\Apps\\Yaoyao.exe'), { path: 'C:\\Apps\\Yaoyao.exe', args: ['--yaoyao-login'] })
  65. })
  66. test('Windows file boundaries handle drive casing, separators, traversal and device paths', () => {
  67. const root = 'C:\\Users\\张三'
  68. for (const path of ['c:\\users\\张三\\Documents\\a.txt', 'C:/Users/张三/Desktop/a.txt', root]) assert.equal(pathInside(root, path, 'win32'), true, path)
  69. for (const path of ['C:\\Users\\张三2\\a.txt', 'D:\\Users\\张三\\a.txt', 'C:\\Users\\张三\\..\\secret']) assert.equal(pathInside(root, path, 'win32'), false, path)
  70. assert.equal(hostPathInput('~\\Documents\\中文.txt', 'win32'), 'Documents\\中文.txt')
  71. for (const path of ['C:secret', '\\\\server\\share', '\\\\?\\C:\\secret', 'a.txt:secret', 'CON', 'x\\NUL.txt', 'x.\\secret', 'a\0b']) assert.throws(() => hostPathInput(path, 'win32'), /路径无效/, path)
  72. assert.equal(hostPathInput('notes:today', 'darwin'), 'notes:today')
  73. assert.equal(pathInside('/home/a', '/home/ab', 'linux'), false)
  74. })