feat: wire Display tab — font size, hover delay, grid columns with immediate apply
This commit is contained in:
+57
-2
@@ -937,6 +937,51 @@ function saveDisplaySettings(settings) {
|
||||
} catch (_) { /* blocked — ok */ }
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply display settings to the live DOM.
|
||||
* Sets --preview-font-size CSS custom property and updates #session-grid
|
||||
* grid-template-columns based on the gridColumns setting.
|
||||
* @param {object} ds - display settings object
|
||||
*/
|
||||
function applyDisplaySettings(ds) {
|
||||
// Apply font size as CSS custom property
|
||||
document.documentElement.style.setProperty('--preview-font-size', ds.fontSize + 'px');
|
||||
|
||||
// Apply grid columns
|
||||
var grid = document.getElementById('session-grid');
|
||||
if (grid) {
|
||||
if (ds.gridColumns === 'auto') {
|
||||
grid.style.removeProperty('grid-template-columns');
|
||||
} else {
|
||||
grid.style.gridTemplateColumns = 'repeat(' + ds.gridColumns + ', 1fr)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a change event on any Display settings control.
|
||||
* Reads current values from form elements, saves via saveDisplaySettings,
|
||||
* and applies via applyDisplaySettings immediately.
|
||||
*/
|
||||
function onDisplaySettingChange() {
|
||||
var ds = loadDisplaySettings();
|
||||
|
||||
var fontSizeEl = document.getElementById('setting-font-size');
|
||||
if (fontSizeEl) ds.fontSize = parseInt(fontSizeEl.value, 10) || ds.fontSize;
|
||||
|
||||
var hoverDelayEl = document.getElementById('setting-hover-delay');
|
||||
if (hoverDelayEl) ds.hoverPreviewDelay = parseInt(hoverDelayEl.value, 10);
|
||||
|
||||
var gridColumnsEl = document.getElementById('setting-grid-columns');
|
||||
if (gridColumnsEl) {
|
||||
var raw = gridColumnsEl.value;
|
||||
ds.gridColumns = raw === 'auto' ? 'auto' : parseInt(raw, 10);
|
||||
}
|
||||
|
||||
saveDisplaySettings(ds);
|
||||
applyDisplaySettings(ds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the settings dialog.
|
||||
* Sets _settingsOpen, calls dialog.showModal(), removes hidden from backdrop,
|
||||
@@ -1126,7 +1171,8 @@ function bindStaticEventListeners() {
|
||||
if (!tile) return;
|
||||
if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; }
|
||||
var name = tile.dataset.session;
|
||||
_previewTimer = setTimeout(function () { showPreview(name); }, 1500);
|
||||
var delay = loadDisplaySettings().hoverPreviewDelay;
|
||||
if (delay > 0) _previewTimer = setTimeout(function () { showPreview(name); }, delay);
|
||||
}, true); // useCapture: true for delegation with mouseenter
|
||||
|
||||
gridEl.addEventListener('mouseleave', function (e) {
|
||||
@@ -1144,7 +1190,8 @@ function bindStaticEventListeners() {
|
||||
if (!item) return;
|
||||
if (_previewTimer) { clearTimeout(_previewTimer); _previewTimer = null; }
|
||||
var name = item.dataset.session;
|
||||
_previewTimer = setTimeout(function () { showPreview(name); }, 1500);
|
||||
var delay = loadDisplaySettings().hoverPreviewDelay;
|
||||
if (delay > 0) _previewTimer = setTimeout(function () { showPreview(name); }, delay);
|
||||
}, true);
|
||||
|
||||
sidebarListEl.addEventListener('mouseleave', function (e) {
|
||||
@@ -1153,6 +1200,11 @@ function bindStaticEventListeners() {
|
||||
hidePreview();
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Display settings — bind change events for immediate apply
|
||||
on($('setting-font-size'), 'change', onDisplaySettingChange);
|
||||
on($('setting-hover-delay'), 'change', onDisplaySettingChange);
|
||||
on($('setting-grid-columns'), 'change', onDisplaySettingChange);
|
||||
}
|
||||
|
||||
// ─── Test-only helpers ────────────────────────────────────────────────────────
|
||||
@@ -1169,6 +1221,7 @@ function _setViewMode(mode) {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initDeviceId();
|
||||
applyDisplaySettings(loadDisplaySettings());
|
||||
document.addEventListener('keydown', trackInteraction);
|
||||
document.addEventListener('click', trackInteraction);
|
||||
document.addEventListener('touchstart', trackInteraction);
|
||||
@@ -1233,6 +1286,8 @@ if (typeof module !== 'undefined' && module.exports) {
|
||||
// Settings
|
||||
loadDisplaySettings,
|
||||
saveDisplaySettings,
|
||||
applyDisplaySettings,
|
||||
onDisplaySettingChange,
|
||||
openSettings,
|
||||
closeSettings,
|
||||
switchSettingsTab,
|
||||
|
||||
Binary file not shown.
@@ -845,3 +845,274 @@ def test_exports_switch_settings_tab() -> None:
|
||||
assert "switchSettingsTab" in exports, (
|
||||
"module.exports must export switchSettingsTab"
|
||||
)
|
||||
|
||||
|
||||
# ── Display tab wiring (task-8) ────────────────────────────────────────────────
|
||||
|
||||
def test_apply_display_settings_function_exists() -> None:
|
||||
"""applyDisplaySettings function must exist."""
|
||||
assert "function applyDisplaySettings" in _JS, (
|
||||
"applyDisplaySettings must be defined in app.js"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_display_settings_sets_font_size_css_property() -> None:
|
||||
"""applyDisplaySettings must set --preview-font-size CSS custom property."""
|
||||
match = re.search(
|
||||
r"function applyDisplaySettings\s*\(\w+\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "applyDisplaySettings function not found"
|
||||
body = match.group(1)
|
||||
assert "--preview-font-size" in body, (
|
||||
"applyDisplaySettings must set --preview-font-size CSS property"
|
||||
)
|
||||
assert "documentElement" in body, (
|
||||
"applyDisplaySettings must call setProperty on document.documentElement"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_display_settings_sets_grid_columns_repeat() -> None:
|
||||
"""applyDisplaySettings must set repeat(N, 1fr) for numeric grid columns."""
|
||||
match = re.search(
|
||||
r"function applyDisplaySettings\s*\(\w+\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "applyDisplaySettings function not found"
|
||||
body = match.group(1)
|
||||
assert "repeat" in body and "1fr" in body, (
|
||||
"applyDisplaySettings must set repeat(N, 1fr) for numeric grid columns"
|
||||
)
|
||||
|
||||
|
||||
def test_apply_display_settings_handles_auto_grid_columns() -> None:
|
||||
"""applyDisplaySettings must handle 'auto' gridColumns by removing inline style."""
|
||||
match = re.search(
|
||||
r"function applyDisplaySettings\s*\(\w+\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "applyDisplaySettings function not found"
|
||||
body = match.group(1)
|
||||
assert "auto" in body, (
|
||||
"applyDisplaySettings must handle 'auto' gridColumns"
|
||||
)
|
||||
assert "session-grid" in body or "gridTemplateColumns" in body, (
|
||||
"applyDisplaySettings must update session-grid or gridTemplateColumns"
|
||||
)
|
||||
|
||||
|
||||
def test_on_display_setting_change_function_exists() -> None:
|
||||
"""onDisplaySettingChange function must exist."""
|
||||
assert "function onDisplaySettingChange" in _JS, (
|
||||
"onDisplaySettingChange must be defined in app.js"
|
||||
)
|
||||
|
||||
|
||||
def test_on_display_setting_change_reads_font_size() -> None:
|
||||
"""onDisplaySettingChange must read from setting-font-size element."""
|
||||
match = re.search(
|
||||
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "onDisplaySettingChange function not found"
|
||||
body = match.group(1)
|
||||
assert "setting-font-size" in body, (
|
||||
"onDisplaySettingChange must read from setting-font-size"
|
||||
)
|
||||
|
||||
|
||||
def test_on_display_setting_change_reads_hover_delay() -> None:
|
||||
"""onDisplaySettingChange must read from setting-hover-delay element."""
|
||||
match = re.search(
|
||||
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "onDisplaySettingChange function not found"
|
||||
body = match.group(1)
|
||||
assert "setting-hover-delay" in body, (
|
||||
"onDisplaySettingChange must read from setting-hover-delay"
|
||||
)
|
||||
|
||||
|
||||
def test_on_display_setting_change_reads_grid_columns() -> None:
|
||||
"""onDisplaySettingChange must read from setting-grid-columns element."""
|
||||
match = re.search(
|
||||
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "onDisplaySettingChange function not found"
|
||||
body = match.group(1)
|
||||
assert "setting-grid-columns" in body, (
|
||||
"onDisplaySettingChange must read from setting-grid-columns"
|
||||
)
|
||||
|
||||
|
||||
def test_on_display_setting_change_calls_save_display_settings() -> None:
|
||||
"""onDisplaySettingChange must call saveDisplaySettings."""
|
||||
match = re.search(
|
||||
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "onDisplaySettingChange function not found"
|
||||
body = match.group(1)
|
||||
assert "saveDisplaySettings" in body, (
|
||||
"onDisplaySettingChange must call saveDisplaySettings"
|
||||
)
|
||||
|
||||
|
||||
def test_on_display_setting_change_calls_apply_display_settings() -> None:
|
||||
"""onDisplaySettingChange must call applyDisplaySettings."""
|
||||
match = re.search(
|
||||
r"function onDisplaySettingChange\s*\(\s*\)\s*\{(.*?)(?=\nfunction |\n// )",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "onDisplaySettingChange function not found"
|
||||
body = match.group(1)
|
||||
assert "applyDisplaySettings" in body, (
|
||||
"onDisplaySettingChange must call applyDisplaySettings"
|
||||
)
|
||||
|
||||
|
||||
def test_hover_preview_no_hardcoded_1500() -> None:
|
||||
"""Hover preview delays must not be hardcoded 1500ms — must use loadDisplaySettings."""
|
||||
match = re.search(
|
||||
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "bindStaticEventListeners function not found"
|
||||
body = match.group(1)
|
||||
# Count occurrences of hardcoded 1500 in the body
|
||||
hardcoded_count = body.count(", 1500)")
|
||||
assert hardcoded_count == 0, (
|
||||
f"Hover preview must not use hardcoded 1500ms delay (found {hardcoded_count} occurrences). "
|
||||
"Use loadDisplaySettings().hoverPreviewDelay instead."
|
||||
)
|
||||
|
||||
|
||||
def test_hover_preview_reads_delay_from_settings() -> None:
|
||||
"""Hover preview must read delay from loadDisplaySettings().hoverPreviewDelay."""
|
||||
match = re.search(
|
||||
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "bindStaticEventListeners function not found"
|
||||
body = match.group(1)
|
||||
assert "loadDisplaySettings" in body and "hoverPreviewDelay" in body, (
|
||||
"bindStaticEventListeners hover preview must read delay from loadDisplaySettings().hoverPreviewDelay"
|
||||
)
|
||||
|
||||
|
||||
def test_hover_preview_skips_when_delay_zero() -> None:
|
||||
"""Hover preview must check delay > 0 before setting setTimeout."""
|
||||
match = re.search(
|
||||
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "bindStaticEventListeners function not found"
|
||||
body = match.group(1)
|
||||
# The body must have a conditional guard on the delay value
|
||||
assert "delay > 0" in body or "> 0" in body, (
|
||||
"bindStaticEventListeners hover preview must guard: if (delay > 0) before setTimeout"
|
||||
)
|
||||
|
||||
|
||||
def test_bind_static_event_listeners_binds_font_size_change() -> None:
|
||||
"""bindStaticEventListeners must bind change event on setting-font-size."""
|
||||
match = re.search(
|
||||
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "bindStaticEventListeners function not found"
|
||||
body = match.group(1)
|
||||
assert "setting-font-size" in body, (
|
||||
"bindStaticEventListeners must reference setting-font-size for change binding"
|
||||
)
|
||||
assert "onDisplaySettingChange" in body, (
|
||||
"bindStaticEventListeners must call onDisplaySettingChange on change"
|
||||
)
|
||||
|
||||
|
||||
def test_bind_static_event_listeners_binds_hover_delay_change() -> None:
|
||||
"""bindStaticEventListeners must bind change event on setting-hover-delay."""
|
||||
match = re.search(
|
||||
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "bindStaticEventListeners function not found"
|
||||
body = match.group(1)
|
||||
assert "setting-hover-delay" in body, (
|
||||
"bindStaticEventListeners must reference setting-hover-delay for change binding"
|
||||
)
|
||||
|
||||
|
||||
def test_bind_static_event_listeners_binds_grid_columns_change() -> None:
|
||||
"""bindStaticEventListeners must bind change event on setting-grid-columns."""
|
||||
match = re.search(
|
||||
r"function bindStaticEventListeners\s*\(\s*\)\s*\{(.*?)\n\}",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "bindStaticEventListeners function not found"
|
||||
body = match.group(1)
|
||||
assert "setting-grid-columns" in body, (
|
||||
"bindStaticEventListeners must reference setting-grid-columns for change binding"
|
||||
)
|
||||
|
||||
|
||||
def test_dom_content_loaded_calls_apply_display_settings() -> None:
|
||||
"""DOMContentLoaded handler must call applyDisplaySettings(loadDisplaySettings())."""
|
||||
# Find the DOMContentLoaded handler block
|
||||
match = re.search(
|
||||
r"DOMContentLoaded.*?\{(.*?)(?=\}\);?\s*\n// |\}\);\s*$)",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "DOMContentLoaded handler not found"
|
||||
body = match.group(1)
|
||||
assert "applyDisplaySettings" in body, (
|
||||
"DOMContentLoaded handler must call applyDisplaySettings(loadDisplaySettings())"
|
||||
)
|
||||
assert "loadDisplaySettings" in body, (
|
||||
"DOMContentLoaded handler must call applyDisplaySettings(loadDisplaySettings())"
|
||||
)
|
||||
|
||||
|
||||
def test_exports_apply_display_settings() -> None:
|
||||
"""module.exports must export applyDisplaySettings."""
|
||||
match = re.search(
|
||||
r"module\.exports\s*=\s*\{(.*?)\};",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "module.exports block not found"
|
||||
exports = match.group(1)
|
||||
assert "applyDisplaySettings" in exports, (
|
||||
"module.exports must export applyDisplaySettings"
|
||||
)
|
||||
|
||||
|
||||
def test_exports_on_display_setting_change() -> None:
|
||||
"""module.exports must export onDisplaySettingChange."""
|
||||
match = re.search(
|
||||
r"module\.exports\s*=\s*\{(.*?)\};",
|
||||
_JS,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert match, "module.exports block not found"
|
||||
exports = match.group(1)
|
||||
assert "onDisplaySettingChange" in exports, (
|
||||
"module.exports must export onDisplaySettingChange"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user