150 lines
5.1 KiB
TypeScript
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)
|
|
}
|