← back to Desktop Dotbar
test/floating-pos.test.js
67 lines
'use strict';
// TK-12225: floatingPosFor must survive macOS renumbering a display's id (seen: 5 -> 6 across a
// reboot). Isolated from the real bar via DOTBAR_TEST_CFG so this never touches the live .barcfg.
process.env.DOTBAR_TEST_CFG = '.tmp-floating-pos-test.barcfg';
const path = require('node:path');
const fs = require('node:fs');
const test = require('node:test');
const assert = require('node:assert');
const dm = require('../electron-main.js');
const TMP_CFG = path.join(__dirname, '..', process.env.DOTBAR_TEST_CFG);
// writeCfg() debounces 200ms; flush synchronously first so no pending timer recreates the
// scratch file after we delete it (must never leak into the live .barcfg either way).
test.after(() => { try { dm.flushCfg(); } catch {} try { fs.unlinkSync(TMP_CFG); } catch {} });
const display = (id, x, y, w, h) => ({ id, workArea: { x, y, width: w, height: h } });
function resetCfg() { dm.cfg.pos = {}; dm.cfg.floating = true; }
test('missing id + orphan pos -> returns the orphan, clamped, and re-keys it to the new id', () => {
resetCfg();
dm.cfg.pos['5'] = { x: 4139, y: 95, w: 149, h: 985, t: 1000 }; // old display id, no longer connected
const newDisplay = display(6, 2294, 0, 1920, 1080); // same monitor, renumbered 5 -> 6
const p = dm.floatingPosFor(newDisplay, [6]); // only id 6 is live now
assert.ok(p, 'must fall back to the orphaned pos instead of returning null');
assert.equal(p.w, 149);
assert.equal(p.h, 985);
assert.ok(p.x >= newDisplay.workArea.x && p.x + p.w <= newDisplay.workArea.x + newDisplay.workArea.width,
'clamped into the new display work area');
assert.ok(dm.cfg.pos['6'], 're-keyed under the new display id so it sticks');
assert.equal(dm.cfg.pos['5'], undefined, 'orphan entry consumed, not duplicated');
});
test('a pos belonging to a display that IS currently connected is never stolen for a different display', () => {
resetCfg();
dm.cfg.pos['2'] = { x: 4139, y: 95, w: 149, h: 985, t: 1000 }; // display 2 is still plugged in
const otherDisplay = display(7, 0, 0, 1920, 1080); // a different display with no saved pos
const p = dm.floatingPosFor(otherDisplay, [2, 7]); // both 2 and 7 are live
assert.equal(p, null, 'display 2\'s pos must not be handed to display 7 while 2 is still connected');
assert.ok(dm.cfg.pos['2'], 'the connected display\'s saved pos is left untouched');
});
test('multiple orphans -> the most recently written one (highest t) wins', () => {
resetCfg();
dm.cfg.pos['5'] = { x: 100, y: 100, w: 149, h: 985, t: 1000 }; // stale
dm.cfg.pos['9'] = { x: 200, y: 200, w: 149, h: 985, t: 5000 }; // most recent
const newDisplay = display(6, 0, 0, 1920, 1080);
const p = dm.floatingPosFor(newDisplay, [6]); // neither 5 nor 9 is connected
assert.equal(p.x, 200, 'picked the higher-t orphan (9), not the stale one (5)');
assert.equal(dm.cfg.pos['9'], undefined, 'the winning orphan is consumed');
assert.ok(dm.cfg.pos['5'], 'the losing orphan is left in place (not silently dropped)');
});
// Negative/regression guard: if the orphan-fallback is ripped back out to the original
// `return p ? clampPosToDisplay(p, display) : null;`, the two assertions below go RED because
// cfg.pos[display.id] is never set for a renumbered display — proving this suite actually
// exercises the fallback rather than passing vacuously.
test('negative: without the fallback this would be null / wrong coords (fault-injection check)', () => {
resetCfg();
dm.cfg.pos['5'] = { x: 321, y: 654, w: 149, h: 985, t: 42 };
const newDisplay = display(6, 0, 0, 1920, 1080);
const naive = dm.cfg.pos && dm.cfg.pos[newDisplay.id]; // what the OLD one-line impl would see
assert.equal(naive, undefined, 'sanity: the id-only lookup the old code used finds nothing');
const p = dm.floatingPosFor(newDisplay, [6]);
assert.ok(p, 'the fallback must still produce a position');
assert.equal(p.x, 321, 'coords must come from the real orphan, not a made-up default');
});