mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-07-07 13:08:54 -07:00
325 lines
8.9 KiB
TypeScript
325 lines
8.9 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { ensureOverlayWindowLevel, updateOverlayWindowBounds } from './overlay-window';
|
|
import {
|
|
handleOverlayWindowBeforeInputEvent,
|
|
handleOverlayWindowBlurred,
|
|
isTabInputForMpvForwarding,
|
|
} from './overlay-window-input';
|
|
|
|
test('isTabInputForMpvForwarding matches bare Tab keydown only', () => {
|
|
assert.equal(
|
|
isTabInputForMpvForwarding({
|
|
type: 'keyDown',
|
|
key: 'Tab',
|
|
code: 'Tab',
|
|
} as Electron.Input),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
isTabInputForMpvForwarding({
|
|
type: 'keyDown',
|
|
key: 'Tab',
|
|
code: 'Tab',
|
|
shift: true,
|
|
} as Electron.Input),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
isTabInputForMpvForwarding({
|
|
type: 'keyUp',
|
|
key: 'Tab',
|
|
code: 'Tab',
|
|
} as Electron.Input),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('handleOverlayWindowBeforeInputEvent forwards Tab to mpv for visible overlays', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBeforeInputEvent({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
input: {
|
|
type: 'keyDown',
|
|
key: 'Tab',
|
|
code: 'Tab',
|
|
} as Electron.Input,
|
|
preventDefault: () => calls.push('prevent-default'),
|
|
sendKeyboardModeToggleRequested: () => calls.push('keyboard-mode'),
|
|
sendLookupWindowToggleRequested: () => calls.push('lookup-toggle'),
|
|
tryHandleOverlayShortcutLocalFallback: () => {
|
|
calls.push('fallback');
|
|
return false;
|
|
},
|
|
forwardTabToMpv: () => calls.push('forward-tab'),
|
|
});
|
|
|
|
assert.equal(handled, true);
|
|
assert.deepEqual(calls, ['prevent-default', 'forward-tab']);
|
|
});
|
|
|
|
test('handleOverlayWindowBeforeInputEvent leaves modal Tab handling alone', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBeforeInputEvent({
|
|
kind: 'modal',
|
|
windowVisible: true,
|
|
input: {
|
|
type: 'keyDown',
|
|
key: 'Tab',
|
|
code: 'Tab',
|
|
} as Electron.Input,
|
|
preventDefault: () => calls.push('prevent-default'),
|
|
sendKeyboardModeToggleRequested: () => calls.push('keyboard-mode'),
|
|
sendLookupWindowToggleRequested: () => calls.push('lookup-toggle'),
|
|
tryHandleOverlayShortcutLocalFallback: () => {
|
|
calls.push('fallback');
|
|
return false;
|
|
},
|
|
forwardTabToMpv: () => calls.push('forward-tab'),
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred skips visible overlay restacking after manual hide', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => false,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred skips Windows visible overlay restacking after focus loss', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => true,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
platform: 'win32',
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred notifies Windows visible overlay blur callback without restacking', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => true,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
onVisibleOverlayBlur: () => {
|
|
calls.push('visible-blur');
|
|
},
|
|
platform: 'win32',
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, ['visible-blur']);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred skips macOS visible overlay restacking after focus loss', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => true,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
platform: 'darwin',
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred skips Linux visible overlay restacking after focus loss', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => true,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
platform: 'linux',
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, []);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred notifies Linux visible overlay blur callback without restacking', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => true,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
onVisibleOverlayBlur: () => {
|
|
calls.push('visible-blur');
|
|
},
|
|
platform: 'linux',
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, ['visible-blur']);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred notifies macOS visible overlay blur callback without restacking', () => {
|
|
const calls: string[] = [];
|
|
|
|
const handled = handleOverlayWindowBlurred({
|
|
kind: 'visible',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => true,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-level');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-top');
|
|
},
|
|
onVisibleOverlayBlur: () => {
|
|
calls.push('visible-blur');
|
|
},
|
|
platform: 'darwin',
|
|
});
|
|
|
|
assert.equal(handled, false);
|
|
assert.deepEqual(calls, ['visible-blur']);
|
|
});
|
|
|
|
test('handleOverlayWindowBlurred preserves modal window stacking', () => {
|
|
const calls: string[] = [];
|
|
|
|
assert.equal(
|
|
handleOverlayWindowBlurred({
|
|
kind: 'modal',
|
|
windowVisible: true,
|
|
isOverlayVisible: () => false,
|
|
ensureOverlayWindowLevel: () => {
|
|
calls.push('ensure-modal');
|
|
},
|
|
moveWindowTop: () => {
|
|
calls.push('move-modal');
|
|
},
|
|
}),
|
|
true,
|
|
);
|
|
|
|
assert.deepEqual(calls, ['ensure-modal']);
|
|
});
|
|
|
|
test('ensureOverlayWindowLevel promotes Linux overlay above fullscreen mpv without changing workspaces', () => {
|
|
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
Object.defineProperty(process, 'platform', {
|
|
configurable: true,
|
|
value: 'linux',
|
|
});
|
|
|
|
const calls: string[] = [];
|
|
|
|
try {
|
|
ensureOverlayWindowLevel({
|
|
getTitle: () => 'SubMiner Overlay',
|
|
moveTop: () => calls.push('move-top'),
|
|
setAlwaysOnTop: (flag: boolean, level?: string, relativeLevel?: number) => {
|
|
calls.push(`always-on-top:${flag}:${level ?? 'none'}:${relativeLevel ?? 0}`);
|
|
},
|
|
setVisibleOnAllWorkspaces: (flag: boolean, options?: { visibleOnFullScreen?: boolean }) => {
|
|
calls.push(
|
|
`all-workspaces:${flag}:${options?.visibleOnFullScreen === true ? 'fullscreen' : 'plain'}`,
|
|
);
|
|
},
|
|
} as never);
|
|
} finally {
|
|
if (originalPlatformDescriptor) {
|
|
Object.defineProperty(process, 'platform', originalPlatformDescriptor);
|
|
}
|
|
}
|
|
|
|
assert.deepEqual(calls, [
|
|
'always-on-top:true:screen-saver:1',
|
|
'all-workspaces:true:fullscreen',
|
|
'move-top',
|
|
]);
|
|
});
|
|
|
|
test('updateOverlayWindowBounds aligns Linux overlay content bounds to mpv geometry', () => {
|
|
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, 'platform');
|
|
const originalHyprlandSignature = process.env.HYPRLAND_INSTANCE_SIGNATURE;
|
|
Object.defineProperty(process, 'platform', {
|
|
configurable: true,
|
|
value: 'linux',
|
|
});
|
|
delete process.env.HYPRLAND_INSTANCE_SIGNATURE;
|
|
|
|
const calls: Array<{ x: number; y: number; width: number; height: number }> = [];
|
|
try {
|
|
updateOverlayWindowBounds({ x: 0, y: 0, width: 3440, height: 1440 }, {
|
|
isDestroyed: () => false,
|
|
getTitle: () => 'SubMiner Overlay',
|
|
getBounds: () => ({ x: 0, y: 0, width: 3440, height: 1440 }),
|
|
getContentBounds: () => ({ x: 0, y: 14, width: 3440, height: 1426 }),
|
|
setBounds: (bounds: { x: number; y: number; width: number; height: number }) => {
|
|
calls.push(bounds);
|
|
},
|
|
} as never);
|
|
} finally {
|
|
if (originalPlatformDescriptor) {
|
|
Object.defineProperty(process, 'platform', originalPlatformDescriptor);
|
|
}
|
|
if (originalHyprlandSignature === undefined) {
|
|
delete process.env.HYPRLAND_INSTANCE_SIGNATURE;
|
|
} else {
|
|
process.env.HYPRLAND_INSTANCE_SIGNATURE = originalHyprlandSignature;
|
|
}
|
|
}
|
|
|
|
assert.deepEqual(calls, [{ x: 0, y: -14, width: 3440, height: 1454 }]);
|
|
});
|