Files
aiturk-hermes-ide/apps/desktop/electron/hud-game-overlay.test.ts
T

308 lines
11 KiB
TypeScript

import assert from 'node:assert/strict'
import { test } from 'vitest'
import {
coversDisplay,
detectFullscreenApp,
findFullscreenAppAnywhere,
gameOverlayStateFor,
INACTIVE_GAME_OVERLAY,
isShellWindow,
startHudGameOverlayWatch
} from './hud-game-overlay'
import type { GameOverlayState } from './hud-game-overlay'
import type { EnumeratedWindow } from './window-below'
const DISPLAY = { x: 0, y: 0, width: 2560, height: 1440 }
const SELF_PID = 4242
const win = (over: Partial<EnumeratedWindow>): EnumeratedWindow => ({
app: 'App',
bounds: { x: 0, y: 0, width: 100, height: 100 },
id: 1,
pid: 1000,
title: '',
...over
})
const fullscreenGame = win({ app: 'Balatro', bounds: { ...DISPLAY }, id: 7, pid: 2000 })
const hudWindow = win({ app: 'Hermes', bounds: { x: 970, y: 1048, width: 620, height: 320 }, id: 2, pid: SELF_PID })
const desktopShell = win({ app: 'Windows Explorer', bounds: { ...DISPLAY }, id: 3, pid: 900 })
// ─── coversDisplay ───────────────────────────────────────────────────────
test('display-sized bounds cover the display', () => {
assert.equal(coversDisplay({ ...DISPLAY }, DISPLAY), true)
})
test('DPI rounding and the 1px-oversize trick stay within the epsilon', () => {
assert.equal(coversDisplay({ x: -1, y: -1, width: 2562, height: 1442 }, DISPLAY), true)
assert.equal(coversDisplay({ x: 1, y: 1, width: 2558, height: 1438 }, DISPLAY), true)
})
test('a maximized window stopping at the taskbar does not cover', () => {
assert.equal(coversDisplay({ x: 0, y: 0, width: 2560, height: 1392 }, DISPLAY), false)
})
test('a fullscreen window on ANOTHER display does not cover this one', () => {
assert.equal(coversDisplay({ x: 2560, y: 0, width: 2560, height: 1440 }, DISPLAY), false)
})
// ─── isShellWindow ───────────────────────────────────────────────────────
test('desktop shells are recognized, real apps are not', () => {
for (const app of ['Windows Explorer', 'explorer.exe', 'Program Manager', 'Finder', 'Dock', 'gnome-shell']) {
assert.equal(isShellWindow(app), true, app)
}
for (const app of ['Balatro', 'Explorer of the Deep', 'Firefox', '']) {
assert.equal(isShellWindow(app), false, app)
}
})
// ─── detectFullscreenApp ─────────────────────────────────────────────────
test('fullscreen game under the HUD is detected', () => {
const found = detectFullscreenApp([hudWindow, fullscreenGame, desktopShell], SELF_PID, DISPLAY)
assert.equal(found?.app, 'Balatro')
})
test('own windows never count, wherever they sit in the z-order', () => {
const selfFullscreen = win({ app: 'Hermes', bounds: { ...DISPLAY }, pid: SELF_PID })
assert.equal(detectFullscreenApp([selfFullscreen, desktopShell], SELF_PID, DISPLAY), null)
})
test('the desktop shell reporting display bounds is not a game', () => {
assert.equal(detectFullscreenApp([hudWindow, desktopShell], SELF_PID, DISPLAY), null)
})
test('a windowed app in FRONT of the game vetoes the overlay', () => {
const editor = win({ app: 'VS Code', bounds: { x: 200, y: 100, width: 1400, height: 900 }, id: 9, pid: 3000 })
assert.equal(detectFullscreenApp([hudWindow, editor, fullscreenGame], SELF_PID, DISPLAY), null)
})
test('zero-area rows (minimized) neither decide nor veto', () => {
const minimized = win({ app: 'Spotify', bounds: { x: 0, y: 0, width: 0, height: 0 }, id: 11, pid: 3100 })
assert.equal(detectFullscreenApp([minimized, fullscreenGame], SELF_PID, DISPLAY)?.app, 'Balatro')
})
test('a window intersecting only another display does not veto', () => {
const otherDisplayWin = win({ app: 'Slack', bounds: { x: 3000, y: 0, width: 800, height: 600 }, id: 12, pid: 3200 })
assert.equal(detectFullscreenApp([otherDisplayWin, fullscreenGame], SELF_PID, DISPLAY)?.app, 'Balatro')
})
test('empty desktop: nothing to detect', () => {
assert.equal(detectFullscreenApp([hudWindow], SELF_PID, DISPLAY), null)
})
test('gameOverlayStateFor maps detection to the pushed state shape', () => {
assert.deepEqual(gameOverlayStateFor([fullscreenGame], SELF_PID, DISPLAY), { active: true, app: 'Balatro' })
assert.deepEqual(gameOverlayStateFor([hudWindow], SELF_PID, DISPLAY), INACTIVE_GAME_OVERLAY)
})
// ─── hysteresis: enter strictly, stay while the game is still there ──────
test('a windowed app on top vetoes ENTERING but not STAYING', () => {
// Clicking the HUD to type de-foregrounds the game and floats every other
// open window above it. Entering must respect that veto; staying must not,
// or the treatment drops the moment the user engages with it.
const editor = win({ app: 'VS Code', bounds: { x: 200, y: 100, width: 1400, height: 900 }, id: 9, pid: 3000 })
const stack = [editor, fullscreenGame]
assert.deepEqual(gameOverlayStateFor(stack, SELF_PID, DISPLAY, false), INACTIVE_GAME_OVERLAY)
assert.deepEqual(gameOverlayStateFor(stack, SELF_PID, DISPLAY, true), { active: true, app: 'Balatro' })
})
test('the game actually closing ends overlay mode even when it was active', () => {
assert.deepEqual(gameOverlayStateFor([hudWindow, desktopShell], SELF_PID, DISPLAY, true), INACTIVE_GAME_OVERLAY)
})
test('findFullscreenAppAnywhere ignores z-order but keeps every other guard', () => {
const selfFullscreen = win({ app: 'Hermes', bounds: { ...DISPLAY }, pid: SELF_PID })
assert.equal(findFullscreenAppAnywhere([selfFullscreen], SELF_PID, DISPLAY), null)
assert.equal(findFullscreenAppAnywhere([desktopShell], SELF_PID, DISPLAY), null)
assert.equal(findFullscreenAppAnywhere([hudWindow, fullscreenGame], SELF_PID, DISPLAY)?.app, 'Balatro')
})
// ─── startHudGameOverlayWatch ────────────────────────────────────────────
interface FakeClock {
fire: () => Promise<void>
cleared: () => boolean
setIntervalFn: typeof setInterval
clearIntervalFn: typeof clearInterval
}
const fakeClock = (): FakeClock => {
let handler: (() => void) | null = null
let cleared = false
return {
fire: async () => {
handler?.()
// The tick is async; let its microtasks drain.
await Promise.resolve()
await Promise.resolve()
},
cleared: () => cleared,
setIntervalFn: ((fn: () => void) => {
handler = fn
return 1 as unknown as ReturnType<typeof setInterval>
}) as typeof setInterval,
clearIntervalFn: (() => {
cleared = true
}) as typeof clearInterval
}
}
const flush = async () => {
await Promise.resolve()
await Promise.resolve()
}
test('watch pushes on change only, never on a repeat answer', async () => {
const clock = fakeClock()
const pushed: Array<{ active: boolean; app: string }> = []
let windows: EnumeratedWindow[] = [hudWindow]
const dispose = startHudGameOverlayWatch({
enumerate: () => Promise.resolve(windows),
displayBounds: () => DISPLAY,
selfPid: SELF_PID,
send: state => pushed.push(state),
setIntervalFn: clock.setIntervalFn,
clearIntervalFn: clock.clearIntervalFn
})
await flush() // the immediate first tick
assert.deepEqual(pushed, [{ active: false, app: '' }])
await clock.fire() // same answer — no push
assert.equal(pushed.length, 1)
windows = [fullscreenGame]
await clock.fire()
assert.deepEqual(pushed[1], { active: true, app: 'Balatro' })
windows = [hudWindow]
await clock.fire()
assert.deepEqual(pushed[2], { active: false, app: '' })
dispose()
assert.equal(clock.cleared(), true)
})
test('watch settles inactive and stops after repeated enumeration failures', async () => {
const clock = fakeClock()
const pushed: Array<{ active: boolean }> = []
startHudGameOverlayWatch({
enumerate: () => Promise.resolve(null),
displayBounds: () => DISPLAY,
selfPid: SELF_PID,
send: state => pushed.push(state),
setIntervalFn: clock.setIntervalFn,
clearIntervalFn: clock.clearIntervalFn
})
await flush() // failure 1 — no conclusion yet
assert.equal(pushed.length, 0)
assert.equal(clock.cleared(), false)
await clock.fire() // failure 2 — settle and stop
assert.deepEqual(pushed, [{ active: false, app: '' }])
assert.equal(clock.cleared(), true)
})
test('a single transient failure does not give up the watch', async () => {
const clock = fakeClock()
const pushed: Array<{ active: boolean }> = []
let fail = true
startHudGameOverlayWatch({
enumerate: () => (fail ? Promise.resolve(null) : Promise.resolve([fullscreenGame])),
displayBounds: () => DISPLAY,
selfPid: SELF_PID,
send: state => pushed.push(state),
setIntervalFn: clock.setIntervalFn,
clearIntervalFn: clock.clearIntervalFn
})
await flush() // failure 1
fail = false
await clock.fire() // recovers
assert.deepEqual(pushed, [{ active: true, app: 'Balatro' }])
assert.equal(clock.cleared(), false)
// …and the failure counter reset: one more null is a transient again.
fail = true
await clock.fire()
assert.equal(clock.cleared(), false)
})
test('disposed watch stops pushing even with a tick in flight', async () => {
const clock = fakeClock()
const pushed: unknown[] = []
let release: (v: EnumeratedWindow[]) => void = () => {}
const dispose = startHudGameOverlayWatch({
enumerate: () =>
new Promise<EnumeratedWindow[]>(resolve => {
release = resolve
}),
displayBounds: () => DISPLAY,
selfPid: SELF_PID,
send: state => pushed.push(state),
setIntervalFn: clock.setIntervalFn,
clearIntervalFn: clock.clearIntervalFn
})
dispose()
release([fullscreenGame])
await flush()
assert.equal(pushed.length, 0)
})
test('the watch carries its own last answer into the next tick', async () => {
// The hysteresis is only correct if `wasActive` comes from what was last
// PUBLISHED; a watch that always passed false would re-veto every tick.
const clock = fakeClock()
const pushed: GameOverlayState[] = []
const editor = win({ app: 'VS Code', bounds: { x: 200, y: 100, width: 1400, height: 900 }, id: 9, pid: 3000 })
let windows: EnumeratedWindow[] = [fullscreenGame]
startHudGameOverlayWatch({
enumerate: () => Promise.resolve(windows),
displayBounds: () => DISPLAY,
selfPid: SELF_PID,
send: state => pushed.push(state),
setIntervalFn: clock.setIntervalFn,
clearIntervalFn: clock.clearIntervalFn
})
await flush()
assert.deepEqual(pushed, [{ active: true, app: 'Balatro' }])
// The user clicks the HUD: the editor is now above the game. Still active, so
// nothing new is published.
windows = [editor, fullscreenGame]
await clock.fire()
assert.equal(pushed.length, 1)
// The game closes for real.
windows = [editor]
await clock.fire()
assert.deepEqual(pushed[1], INACTIVE_GAME_OVERLAY)
})