feedBack-desktop/tests/window-bounds.test.js
gionnibgud 6349ed4c5f
Some checks are pending
Addon CI / addon (arm64, macos-14, mac) (push) Waiting to run
Addon CI / addon (x64, ubuntu-22.04, linux) (push) Waiting to run
Addon CI / addon (x64, windows-latest, win) (push) Waiting to run
Ship CI / CI (push) Waiting to run
feat(window): persist main window size/position across launches (#97)
* feat(window): persist main window size/position across launches

The main window always opened at a fixed 1400x900, forcing a manual
resize every session. Save the window geometry (normal bounds +
maximized flag) to the existing desktop prefs store on close, and
restore it in createWindow.

Saved bounds are validated by a pure sanitizer against the current
display layout before use, so stale state degrades safely instead of
producing an off-screen or absurd window:
- garbage/partial config -> 1400x900 centered defaults
- size clamped between the 800x600 window minimums and the largest
  display's workArea
- position kept only when the window overlaps a display by at least
  100x50 px (unplugged monitor / resolution change -> re-center);
  negative multi-monitor coordinates remain valid
- maximized sessions save getNormalBounds() and re-maximize on
  restore; fullscreen deliberately restores windowed

No new dependency; reuses get/setDesktopConfig (atomic write,
fail-soft) in soundfont-manager.ts. The store file is already in the
reset-app-settings delete-set, so a config reset also resets bounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>

* fix(window): don't crash shutdown if bounds persistence write fails

The close listener called setDesktopConfig synchronously with no error
handling; a disk-full or permissions failure during the write would throw
unhandled inside the close handler, risking a shutdown crash. Wrap the
write in try/catch and log a warning instead. Flagged by CodeRabbit on PR #97.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: gionnibgud <gionnibgud@gmail.com>

---------

Signed-off-by: gionnibgud <gionnibgud@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:41:47 +02:00

60 lines
2.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const test = require('node:test');
const assert = require('node:assert/strict');
const { loadTs } = require('./_load-ts');
const { sanitizeWindowBounds, DEFAULT_WIDTH, DEFAULT_HEIGHT, MIN_WIDTH, MIN_HEIGHT } =
loadTs('src/main/window-bounds.ts');
// A common single-display workArea (1920×1080 minus a 40px taskbar).
const PRIMARY = { x: 0, y: 0, width: 1920, height: 1040 };
const DEFAULTS = { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, maximized: false };
test('valid bounds on a matching display round-trip', () => {
const saved = { x: 100, y: 50, width: 1200, height: 800, maximized: true };
assert.deepEqual(sanitizeWindowBounds(saved, [PRIMARY]), saved);
});
test('garbage input falls back to defaults', () => {
for (const bad of [undefined, null, 'wat', 42, {}, { x: 1, y: 2 }, { x: 'a', y: 0, width: 1200, height: 800 }, { x: NaN, y: 0, width: 1200, height: 800 }, { x: 0, y: 0, width: Infinity, height: 800 }]) {
assert.deepEqual(sanitizeWindowBounds(bad, [PRIMARY]), DEFAULTS);
}
});
test('empty display list falls back to defaults', () => {
assert.deepEqual(sanitizeWindowBounds({ x: 0, y: 0, width: 1200, height: 800 }, []), DEFAULTS);
});
test('oversize bounds clamp to the largest display workArea', () => {
const out = sanitizeWindowBounds({ x: 0, y: 0, width: 5000, height: 4000 }, [PRIMARY]);
assert.deepEqual(out, { x: 0, y: 0, width: 1920, height: 1040, maximized: false });
});
test('undersize bounds clamp up to the window minimums', () => {
const out = sanitizeWindowBounds({ x: 10, y: 10, width: 300, height: 200 }, [PRIMARY]);
assert.deepEqual(out, { x: 10, y: 10, width: MIN_WIDTH, height: MIN_HEIGHT, maximized: false });
});
test('position on a now-unplugged monitor is dropped, size kept', () => {
// Saved on a second display to the right that no longer exists.
const out = sanitizeWindowBounds({ x: 2000, y: 100, width: 1200, height: 800 }, [PRIMARY]);
assert.deepEqual(out, { width: 1200, height: 800, maximized: false });
});
test('negative coordinates on a left-of-primary monitor are kept', () => {
const leftMonitor = { x: -1920, y: 0, width: 1920, height: 1040 };
const saved = { x: -1800, y: 100, width: 1200, height: 800 };
const out = sanitizeWindowBounds(saved, [leftMonitor, PRIMARY]);
assert.deepEqual(out, { ...saved, maximized: false });
});
test('sliver overlap below the grab threshold drops the position', () => {
// Only 50px of the window's left edge on screen — not enough to grab.
const out = sanitizeWindowBounds({ x: 1870, y: 100, width: 1200, height: 800 }, [PRIMARY]);
assert.deepEqual(out, { width: 1200, height: 800, maximized: false });
});
test('fractional coordinates are rounded to integers', () => {
const out = sanitizeWindowBounds({ x: 10.6, y: 20.4, width: 1200.5, height: 800.2 }, [PRIMARY]);
assert.deepEqual(out, { x: 11, y: 20, width: 1201, height: 800, maximized: false });
});