diff --git a/test/dialog-form-problems.test.mjs b/test/dialog-form-problems.test.mjs new file mode 100644 index 00000000..6c63ff81 --- /dev/null +++ b/test/dialog-form-problems.test.mjs @@ -0,0 +1,132 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { generalProblems } from '../test-build/editors/general-form-state.js'; +import { markerProblems } from '../test-build/editors/marker-form-state.js'; +import { spaceDialogProblems } from '../test-build/editors/space-form-state.js'; +import { GRID_CELL_CM_MAX, GRID_CELL_CM_MIN } from '../test-build/grid-scale.js'; + +const fields = (problems) => problems.map(({ field, message }) => [field, message]); + +const generalDraft = (glowRadiusInput, northDegInput = '') => ({ + glowRadiusInput, + northDegInput, +}); + +test('#614 generalProblems validates required glow text without reviving the typed value', () => { + const glowError = [['gs-glow-radius', 'gs.error_glow_radius']]; + assert.deepEqual(fields(generalProblems(generalDraft(''))), glowError); + assert.deepEqual(fields(generalProblems(generalDraft(' '))), glowError); + assert.deepEqual(fields(generalProblems(generalDraft('glow'))), glowError); + assert.deepEqual(fields(generalProblems(generalDraft('0'))), glowError); + assert.deepEqual(fields(generalProblems(generalDraft('-0'))), glowError); + assert.deepEqual(fields(generalProblems(generalDraft('-1'))), glowError); + assert.deepEqual(fields(generalProblems(generalDraft('0,01'))), []); + assert.deepEqual(fields(generalProblems(generalDraft('25'))), []); +}); + +test('#614 generalProblems keeps north optional but enforces integer compass bounds', () => { + const northError = [['gs-north', 'gs.error_north']]; + for (const raw of ['', ' ', '0', '359']) { + assert.deepEqual(fields(generalProblems(generalDraft('25', raw))), [], raw); + } + for (const raw of ['north', '12,5', '-1', '360']) { + assert.deepEqual(fields(generalProblems(generalDraft('25', raw))), northError, raw); + } +}); + +const baseSpace = (patch = {}) => ({ + title: 'Ground floor', + cellCm: 5, + cellCmInput: '5', + source: 'draw', + planFile: null, + planUrl: null, + fillMode: 'none', + tempMin: 18, + tempMax: 26, + tempMinInput: '18', + tempMaxInput: '26', + northDeg: null, + northDegInput: '', + ...patch, +}); + +const spaceFields = (patch = {}, imperial = false) => + fields(spaceDialogProblems(baseSpace(patch), 'space', imperial)); + +test('#614 spaceDialogProblems validates raw scale text and persisted bounds', () => { + const scaleError = [['space-cell-cm', 'space.error_scale']]; + for (const raw of ['', ' ', 'scale', '0', String(GRID_CELL_CM_MIN - 0.01), String(GRID_CELL_CM_MAX + 1)]) { + assert.deepEqual(spaceFields({ cellCmInput: raw }), scaleError, raw); + } + for (const raw of [String(GRID_CELL_CM_MIN), '1,5', String(GRID_CELL_CM_MAX)]) { + assert.deepEqual(spaceFields({ cellCmInput: raw }), [], raw); + } +}); + +test('#614 spaceDialogProblems reports each temperature input and then the range', () => { + assert.deepEqual(spaceFields({ fillMode: 'temp', tempMinInput: '', tempMaxInput: ' ' }), [ + ['space-temp-min', 'space.error_temp_value'], + ['space-temp-max', 'space.error_temp_value'], + ]); + assert.deepEqual(spaceFields({ fillMode: 'temp', tempMinInput: 'cold', tempMaxInput: '24' }), [ + ['space-temp-min', 'space.error_temp_value'], + ]); + assert.deepEqual(spaceFields({ fillMode: 'temp', tempMinInput: '18,5', tempMaxInput: '24,5' }), []); + assert.deepEqual(spaceFields({ fillMode: 'temp', tempMinInput: '20', tempMaxInput: '20' }), [ + ['space-temp-max', 'space.error_temp_range'], + ]); + assert.deepEqual(spaceFields({ fillMode: 'temp', tempMinInput: '21', tempMaxInput: '20' }), [ + ['space-temp-max', 'space.error_temp_range'], + ]); +}); + +test('#614 spaceDialogProblems validates custom north only and keeps exact bounds', () => { + const northError = [['space-north-deg', 'space.error_north']]; + for (const raw of ['', ' ', 'north', '12,5', '-1', '360']) { + assert.deepEqual(spaceFields({ northDeg: 0, northDegInput: raw }), northError, raw); + } + for (const raw of ['0', '359']) { + assert.deepEqual(spaceFields({ northDeg: 0, northDegInput: raw }), [], raw); + } + assert.deepEqual(spaceFields({ northDeg: null, northDegInput: 'not used' }), []); +}); + +const baseMarker = (patch = {}) => ({ + binding: 'virtual', + bindingMode: 'virtual', + name: 'Virtual lamp', + tapAction: 'more-info', + tapTarget: '', + valueBadgeTouched: false, + valueBadgeEnabled: false, + valueBadgeSource: null, + ...patch, +}); + +test('#614 markerProblems exposes virtual name, Run target and badge source inline', () => { + assert.deepEqual(fields(markerProblems(baseMarker({ + name: ' ', + tapAction: 'run', + valueBadgeTouched: true, + valueBadgeEnabled: true, + }))), [ + ['marker-name', 'marker.error_virtual_name'], + ['marker-run-target', 'marker.error_run_target'], + ['marker-value-badge-source', 'marker.error_value_badge_source'], + ]); + + assert.deepEqual(fields(markerProblems(baseMarker({ name: 'Lamp' }))), []); + assert.deepEqual(fields(markerProblems(baseMarker({ tapAction: 'run', tapTarget: 'script.good' }))), []); + assert.deepEqual(fields(markerProblems(baseMarker({ + valueBadgeTouched: true, + valueBadgeEnabled: true, + valueBadgeSource: { entity: 'sensor.value' }, + }))), []); + assert.deepEqual(fields(markerProblems(baseMarker({ + valueBadgeTouched: false, + valueBadgeEnabled: true, + valueBadgeSource: null, + }))), []); +}); diff --git a/tsconfig.test.json b/tsconfig.test.json index ceb69b3d..3f2f358b 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -23,7 +23,7 @@ "src/devices.ts", "src/device-inbox.ts", "src/virtual-light-state.ts", "src/config-store.ts", "src/config-reload-authority.ts", "src/config-write-conflict.ts", "src/summary-panel.ts", "src/summary-panel-metrics.ts", "src/summary-panel-i18n.ts", "src/summary-panel-identity.ts", "src/summary-panel-picker.ts", "src/summary-panel-runtime-loaded.ts", "src/backdrop-probe.ts", - "src/types.ts", "src/editors/dialog-baseline.ts", + "src/types.ts", "src/editors/dialog-baseline.ts", "src/editors/general-form-state.ts", "src/editors/space-form-state.ts", "src/editors/marker-form-state.ts", "src/space-geometry.ts", "src/junction-limits.ts", "src/space-order.ts", "src/card-editor-validation.ts", "src/signing.ts", "src/initial-load.ts", "src/space-model-selection.ts", "src/space-dialog.ts",