macos-web/tests/helpers.ts
2026-07-20 14:30:44 +08:00

150 lines
5.1 KiB
TypeScript

/**
* Vitest helpers for macOS-web tests.
* Mirrors the original tests/helpers.js but uses Vitest + happy-dom.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { initSystem } from '../src/system'
/** Set up the full DOM structure needed by the app (mirrors index.html) */
export function setupDOM() {
document.body.innerHTML = `
<div id="overlay-brightness" aria-hidden="true"></div>
<div id="overlay-nightshift" aria-hidden="true"></div>
<div id="desktop" class="hidden" aria-label="桌面">
<div id="desktop-icons"></div>
</div>
<div id="window-layer"></div>
<header id="menubar" class="hidden">
<div id="menubar-left"></div>
<div id="menubar-right"></div>
</header>
<div id="dock-hotzone" aria-hidden="true"></div>
<nav id="dock" class="hidden" aria-label="程序坞"></nav>
<div id="control-center" class="popover hidden" role="dialog" aria-label="控制中心"></div>
<aside id="notification-center" class="hidden" aria-label="通知中心"></aside>
<div id="spotlight" class="hidden" role="dialog" aria-label="聚焦搜索">
<div id="spotlight-box">
<div id="spotlight-input-row">
<svg viewBox="0 0 24 24" width="20" height="20" class="sp-icon"><circle cx="10.5" cy="10.5" r="6.5" fill="none" stroke="currentColor" stroke-width="2.4"/><line x1="15.5" y1="15.5" x2="21" y2="21" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>
<input id="spotlight-input" type="text" placeholder="聚焦搜索" autocomplete="off" spellcheck="false">
</div>
<div id="spotlight-results"></div>
</div>
</div>
<div id="banner-container"></div>
<div id="lockscreen" class="hidden">
<div id="lockscreen-bg"></div>
<div id="lockscreen-main">
<div id="lock-date"></div>
<div id="lock-time"></div>
</div>
<div id="lockscreen-user">
<img id="lock-avatar" src="/assets/icons/avatar.svg" alt="用户头像">
<div id="lock-username">客人用户</div>
<div id="lock-password-row" class="hidden">
<input id="lock-password" type="password" placeholder="输入密码" aria-label="密码">
</div>
<div id="lock-hint">点按或按 Enter 键解锁</div>
<div id="lock-error" class="hidden">密码不正确,请重试</div>
</div>
</div>
<div id="screensaver" class="hidden"></div>
<div id="bootscreen">
<div id="boot-apple"><svg viewBox="0 0 384 512" width="72" height="96" aria-hidden="true"><path fill="#fff" d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"/></svg></div>
<div id="boot-progress"><div id="boot-progress-fill"></div></div>
</div>
<div id="poweroff" class="hidden">
<div id="poweroff-text">已关机<br><span>点按屏幕以开机</span></div>
</div>
`
}
/** Boot sequence: simulate what Sys.boot() does but skip animations */
export async function bootSystem() {
setupDOM()
// Init the system (this registers apps, sets up FS, etc.)
initSystem()
// Simulate boot completion by forcing immediate unlock
const { Sys } = await import('../src/composables/useSys')
Sys.boot()
// Fast-forward boot animation
await tick(3100)
}
/** Simulate time passage */
export function tick(ms: number): Promise<void> {
return new Promise(r => setTimeout(r, ms))
}
/** Wait for an element to be visible */
export async function waitFor(selector: string, timeout = 5000): Promise<HTMLElement> {
const start = Date.now()
while (Date.now() - start < timeout) {
const el = document.querySelector(selector) as HTMLElement
if (el && !el.classList.contains('hidden')) return el
await tick(50)
}
throw new Error(`Timeout waiting for: ${selector}`)
}
/** Get global WM */
export function getWM(): any {
return (window as any).WM
}
/** Get global FS */
export function getFS(): any {
return (window as any).FS
}
/** Get global Apps */
export function getApps(): any {
return (window as any).Apps
}
/** Get global Sys */
export function getSys(): any {
return (window as any).Sys
}
/** Get global Notify */
export function getNotify(): any {
return (window as any).Notify
}
/** Open an app and wait for window */
export async function openApp(appId: string, args?: any) {
const Apps = getApps()
const WM = getWM()
Apps.open(appId, args)
await tick(200)
// Return window count
return WM.windows.length
}
/** Close active window */
export async function closeActiveWindow() {
const WM = getWM()
const win = WM.activeWin
if (win) await WM.close(win)
await tick(200)
}
/** Get window count */
export function winCount(): number {
return getWM().windows.length
}
/** Get window titles */
export function winTitles(): string[] {
return getWM().windows.map((w: any) => w.appId + ':' + w.title)
}
/** Unlock the system */
export async function unlockSystem() {
const Sys = getSys()
// Simulate clicking the lockscreen
Sys.unlock()
await tick(500)
}