mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-25 12:55:18 -07:00
200 lines
4.9 KiB
TypeScript
200 lines
4.9 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
HyprlandWindowTracker,
|
|
isHyprlandGeometryEvent,
|
|
parseHyprctlClients,
|
|
parseHyprctlMonitors,
|
|
resolveHyprlandWindowGeometry,
|
|
selectHyprlandMpvWindow,
|
|
type HyprlandClient,
|
|
type HyprlandMonitor,
|
|
} from './hyprland-tracker';
|
|
|
|
function makeClient(overrides: Partial<HyprlandClient> = {}): HyprlandClient {
|
|
return {
|
|
address: '0x1',
|
|
class: 'mpv',
|
|
initialClass: 'mpv',
|
|
at: [0, 0],
|
|
size: [1280, 720],
|
|
mapped: true,
|
|
hidden: false,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeMonitor(overrides: Partial<HyprlandMonitor> = {}): HyprlandMonitor {
|
|
return {
|
|
id: 0,
|
|
x: 0,
|
|
y: 0,
|
|
width: 1920,
|
|
height: 1080,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test('selectHyprlandMpvWindow ignores hidden and unmapped mpv clients', () => {
|
|
const selected = selectHyprlandMpvWindow(
|
|
[
|
|
makeClient({
|
|
address: '0xhidden',
|
|
hidden: true,
|
|
}),
|
|
makeClient({
|
|
address: '0xunmapped',
|
|
mapped: false,
|
|
}),
|
|
makeClient({
|
|
address: '0xvisible',
|
|
at: [100, 200],
|
|
size: [1920, 1080],
|
|
}),
|
|
],
|
|
{
|
|
targetMpvSocketPath: null,
|
|
activeWindowAddress: null,
|
|
getWindowCommandLine: () => null,
|
|
},
|
|
);
|
|
|
|
assert.equal(selected?.address, '0xvisible');
|
|
});
|
|
|
|
test('selectHyprlandMpvWindow prefers active visible window among socket matches', () => {
|
|
const commandLines = new Map<string, string>([
|
|
['10', 'mpv --input-ipc-server=/tmp/subminer.sock first.mkv'],
|
|
['20', 'mpv --input-ipc-server=/tmp/subminer.sock second.mkv'],
|
|
]);
|
|
|
|
const selected = selectHyprlandMpvWindow(
|
|
[
|
|
makeClient({
|
|
address: '0xfirst',
|
|
pid: 10,
|
|
}),
|
|
makeClient({
|
|
address: '0xsecond',
|
|
pid: 20,
|
|
}),
|
|
],
|
|
{
|
|
targetMpvSocketPath: '/tmp/subminer.sock',
|
|
activeWindowAddress: '0xsecond',
|
|
getWindowCommandLine: (pid) => commandLines.get(String(pid)) ?? null,
|
|
},
|
|
);
|
|
|
|
assert.equal(selected?.address, '0xsecond');
|
|
});
|
|
|
|
test('selectHyprlandMpvWindow matches mpv by initialClass when class is blank', () => {
|
|
const selected = selectHyprlandMpvWindow(
|
|
[
|
|
makeClient({
|
|
address: '0xinitial',
|
|
class: '',
|
|
initialClass: 'mpv',
|
|
}),
|
|
],
|
|
{
|
|
targetMpvSocketPath: null,
|
|
activeWindowAddress: null,
|
|
getWindowCommandLine: () => null,
|
|
},
|
|
);
|
|
|
|
assert.equal(selected?.address, '0xinitial');
|
|
});
|
|
|
|
test('parseHyprctlClients tolerates non-json prefix output', () => {
|
|
const clients = parseHyprctlClients(`ok
|
|
[{"address":"0x1","class":"mpv","initialClass":"mpv","at":[1,2],"size":[3,4]}]`);
|
|
|
|
assert.deepEqual(clients, [
|
|
{
|
|
address: '0x1',
|
|
class: 'mpv',
|
|
initialClass: 'mpv',
|
|
at: [1, 2],
|
|
size: [3, 4],
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('parseHyprctlMonitors returns null for malformed JSON output', () => {
|
|
assert.equal(parseHyprctlMonitors('not-json'), null);
|
|
assert.equal(parseHyprctlMonitors('[{"id":0,"x":0,"y":0,"width":1920'), null);
|
|
});
|
|
|
|
test('isHyprlandGeometryEvent treats geometry events as geometry-changing only', () => {
|
|
assert.equal(isHyprlandGeometryEvent('fullscreenv2'), true);
|
|
assert.equal(isHyprlandGeometryEvent('workspacev2'), true);
|
|
assert.equal(isHyprlandGeometryEvent('windowtitle'), false);
|
|
assert.equal(isHyprlandGeometryEvent('windowtitlev2'), false);
|
|
assert.equal(isHyprlandGeometryEvent('activewindowv2'), false);
|
|
});
|
|
|
|
test('resolveHyprlandWindowGeometry uses monitor bounds for fullscreen clients', () => {
|
|
const geometry = resolveHyprlandWindowGeometry(
|
|
makeClient({
|
|
at: [60, 80],
|
|
size: [1280, 720],
|
|
monitor: 1,
|
|
fullscreen: 2,
|
|
fullscreenClient: 2,
|
|
}),
|
|
[
|
|
makeMonitor({ id: 0, x: 0, y: 0, width: 1920, height: 1080 }),
|
|
makeMonitor({ id: 1, x: 1920, y: 0, width: 2560, height: 1440 }),
|
|
],
|
|
);
|
|
|
|
assert.deepEqual(geometry, {
|
|
x: 1920,
|
|
y: 0,
|
|
width: 2560,
|
|
height: 1440,
|
|
});
|
|
});
|
|
|
|
test('resolveHyprlandWindowGeometry uses monitor bounds for client-requested fullscreen', () => {
|
|
const geometry = resolveHyprlandWindowGeometry(
|
|
makeClient({
|
|
at: [0, 28],
|
|
size: [1920, 1052],
|
|
monitor: 0,
|
|
fullscreen: 0,
|
|
fullscreenClient: 2,
|
|
}),
|
|
[makeMonitor({ id: 0, x: 0, y: 0, width: 1920, height: 1080 })],
|
|
);
|
|
|
|
assert.deepEqual(geometry, {
|
|
x: 0,
|
|
y: 0,
|
|
width: 1920,
|
|
height: 1080,
|
|
});
|
|
});
|
|
|
|
test('HyprlandWindowTracker re-emits focus callback on active window events for z-order refresh', () => {
|
|
const calls: string[] = [];
|
|
const tracker = new HyprlandWindowTracker();
|
|
const privateTracker = tracker as unknown as {
|
|
handleSocketEvent: (event: string) => void;
|
|
pollGeometry: () => void;
|
|
};
|
|
privateTracker.pollGeometry = () => {
|
|
calls.push('poll');
|
|
};
|
|
tracker.onWindowFocusChange = (focused) => {
|
|
calls.push(`focus:${focused}`);
|
|
};
|
|
|
|
privateTracker.handleSocketEvent('activewindowv2>>0xmpv');
|
|
|
|
assert.deepEqual(calls, ['poll', 'focus:false']);
|
|
});
|