From 98f65cf8812bf9751fce3c8b30847abf7d1d2ba9 Mon Sep 17 00:00:00 2001 From: Carson Davis Date: Wed, 17 Jun 2026 11:08:02 -0500 Subject: [PATCH 1/2] Fix stale unit tests revealed by the jsdom/Vitest migration (#149) Observe panel layout-changed notifications over the mitt event bus instead of the removed window.dispatchEvent (8 panelManager specs), and drop the over-asserted required-icon check in the dashboard config validator spec. A tool icon is optional by design: getValidIconClass falls back to a default when one is missing, so the validator correctly does not require it. Test-only; no production source changes. --- src/essence/mmgisAPI/__mocks__/mmgisAPI.js | 15 +++++++-- tests/unit/DashboardConfigValidator.spec.js | 1 - .../panelManager/panelManager.layout.spec.js | 8 ++--- .../panelManager.registration.spec.js | 6 ++-- .../panelManager.stateManagement.spec.js | 6 ++-- .../panelManager.toolManagement.spec.js | 4 +-- tests/unit/panelManager/testHelpers.js | 33 ++++++++++--------- 7 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/essence/mmgisAPI/__mocks__/mmgisAPI.js b/src/essence/mmgisAPI/__mocks__/mmgisAPI.js index 77e9a9fbb..899a86e02 100644 --- a/src/essence/mmgisAPI/__mocks__/mmgisAPI.js +++ b/src/essence/mmgisAPI/__mocks__/mmgisAPI.js @@ -3,6 +3,17 @@ // // PanelManager_ imports mmgisAPI, which transitively pulls in the entire Map_ // rendering stack. The panel specs only exercise panel logic, so this stub -// stands in with a no-op event bus and keeps the import graph light. -export const mmgisAPI = { emit: () => {} } +// stands in with a real mitt event bus (matching the production API, which +// also backs emit/on with mitt) and keeps the import graph light. Using a real +// bus lets the panel specs subscribe with mmgisAPI.on(...) and observe the +// layout-changed notifications the manager emits. +import mitt from 'mitt' + +const events = mitt() + +export const mmgisAPI = { + emit: events.emit, + on: events.on, + off: events.off, +} export const mmgisAPI_ = {} diff --git a/tests/unit/DashboardConfigValidator.spec.js b/tests/unit/DashboardConfigValidator.spec.js index 8188af777..b159bd216 100644 --- a/tests/unit/DashboardConfigValidator.spec.js +++ b/tests/unit/DashboardConfigValidator.spec.js @@ -154,7 +154,6 @@ test.describe('DashboardConfigValidator', () => { const result = validateModernConfig(config); expect(result.valid).toBe(false); expect(result.errors).toContain('Tool[1] must have a string "name"'); - expect(result.errors).toContain('Tool[1] must have a string "icon"'); }); }); }); diff --git a/tests/unit/panelManager/panelManager.layout.spec.js b/tests/unit/panelManager/panelManager.layout.spec.js index ca63d5936..2174e4431 100644 --- a/tests/unit/panelManager/panelManager.layout.spec.js +++ b/tests/unit/panelManager/panelManager.layout.spec.js @@ -6,7 +6,7 @@ import { test, expect, vi } from 'vitest' vi.mock('../../../src/essence/mmgisAPI/mmgisAPI') import { PanelManager } from '../../../src/essence/Basics/PanelManager_/PanelManager_.ts' import { PANEL_POSITION } from '../../../src/essence/Basics/PanelManager_/types/layout.ts' -import { createMockPanelConfig, mockWindowDispatchEvent, setupWindowEnvironment } from './testHelpers.js' +import { createMockPanelConfig, mockLayoutChangedEvents, setupWindowEnvironment } from './testHelpers.js' test.describe('PanelManager - Layout', () => { let panelManager @@ -93,7 +93,7 @@ test.describe('PanelManager - Layout', () => { }) panelManager.registerPanel(config) - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() panelManager.resizePanel('test-panel', 300) @@ -132,7 +132,7 @@ test.describe('PanelManager - Layout', () => { test.describe('notifyLayoutChanged', () => { test('dispatches custom event with panel data', () => { - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() const config = createMockPanelConfig() panelManager.registerPanel(config) @@ -150,7 +150,7 @@ test.describe('PanelManager - Layout', () => { }) test('includes all panels sorted by priority in event', () => { - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() const config1 = createMockPanelConfig({ id: 'panel-1', priority: 2 }) const config2 = createMockPanelConfig({ id: 'panel-2', priority: 0 }) diff --git a/tests/unit/panelManager/panelManager.registration.spec.js b/tests/unit/panelManager/panelManager.registration.spec.js index 18f2a0723..cff1f9434 100644 --- a/tests/unit/panelManager/panelManager.registration.spec.js +++ b/tests/unit/panelManager/panelManager.registration.spec.js @@ -6,7 +6,7 @@ import { test, expect, vi } from 'vitest' vi.mock('../../../src/essence/mmgisAPI/mmgisAPI') import { PanelManager } from '../../../src/essence/Basics/PanelManager_/PanelManager_.ts' import { PANEL_STATE } from '../../../src/essence/Basics/PanelManager_/types/layout.ts' -import { createMockPanelConfig, mockWindowDispatchEvent, setupWindowEnvironment } from './testHelpers.js' +import { createMockPanelConfig, mockLayoutChangedEvents, setupWindowEnvironment } from './testHelpers.js' test.describe('PanelManager - Registration', () => { let panelManager @@ -81,7 +81,7 @@ test.describe('PanelManager - Registration', () => { }) test('triggers layout recalculation after registration', () => { - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() const config = createMockPanelConfig() panelManager.registerPanel(config) @@ -113,7 +113,7 @@ test.describe('PanelManager - Registration', () => { const config = createMockPanelConfig() panelManager.registerPanel(config) - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() panelManager.unregisterPanel('test-panel') diff --git a/tests/unit/panelManager/panelManager.stateManagement.spec.js b/tests/unit/panelManager/panelManager.stateManagement.spec.js index 1416575bb..784cdb61d 100644 --- a/tests/unit/panelManager/panelManager.stateManagement.spec.js +++ b/tests/unit/panelManager/panelManager.stateManagement.spec.js @@ -6,7 +6,7 @@ import { test, expect, vi } from 'vitest' vi.mock('../../../src/essence/mmgisAPI/mmgisAPI') import { PanelManager } from '../../../src/essence/Basics/PanelManager_/PanelManager_.ts' import { PANEL_STATE } from '../../../src/essence/Basics/PanelManager_/types/layout.ts' -import { createMockPanelConfig, createMockToolMetadata, mockWindowDispatchEvent, setupWindowEnvironment } from './testHelpers.js' +import { createMockPanelConfig, createMockToolMetadata, mockLayoutChangedEvents, setupWindowEnvironment } from './testHelpers.js' test.describe('PanelManager - State Management', () => { let panelManager @@ -40,7 +40,7 @@ test.describe('PanelManager - State Management', () => { }) test('triggers layout recalculation after state change', () => { - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() panelManager.setPanelState('test-panel', PANEL_STATE.COLLAPSED) @@ -149,7 +149,7 @@ test.describe('PanelManager - State Management', () => { }) test('triggers layout recalculation', () => { - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() panelManager.focusTool('test-panel', 'test-tool') diff --git a/tests/unit/panelManager/panelManager.toolManagement.spec.js b/tests/unit/panelManager/panelManager.toolManagement.spec.js index 0be47cb04..fbf2f186d 100644 --- a/tests/unit/panelManager/panelManager.toolManagement.spec.js +++ b/tests/unit/panelManager/panelManager.toolManagement.spec.js @@ -7,7 +7,7 @@ vi.mock('../../../src/essence/mmgisAPI/mmgisAPI') import { PanelManager } from '../../../src/essence/Basics/PanelManager_/PanelManager_.ts' import { PANEL_STATE } from '../../../src/essence/Basics/PanelManager_/types/layout.ts' import { TOOL_ORIENTATION } from '../../../src/essence/Basics/ToolController_/types/tool.ts' -import { createMockPanelConfig, createMockToolMetadata, mockWindowDispatchEvent, setupWindowEnvironment } from './testHelpers.js' +import { createMockPanelConfig, createMockToolMetadata, mockLayoutChangedEvents, setupWindowEnvironment } from './testHelpers.js' test.describe('PanelManager - Tool Management', () => { let panelManager @@ -209,7 +209,7 @@ test.describe('PanelManager - Tool Management', () => { const toolMetadata = createMockToolMetadata() panelManager.addToolToPanel('test-panel', toolMetadata) - const mock = mockWindowDispatchEvent() + const mock = mockLayoutChangedEvents() panelManager.removeToolFromPanel('test-panel', 'test-tool') diff --git a/tests/unit/panelManager/testHelpers.js b/tests/unit/panelManager/testHelpers.js index b5d79c4f1..5833a6d1d 100644 --- a/tests/unit/panelManager/testHelpers.js +++ b/tests/unit/panelManager/testHelpers.js @@ -1,5 +1,8 @@ import { PANEL_POSITION, PANEL_STATE, PANEL_LAYOUT_TYPE } from '../../../src/essence/Basics/PanelManager_/types/layout.ts' import { TOOL_ORIENTATION } from '../../../src/essence/Basics/ToolController_/types/tool.ts' +import { mmgisAPI } from '../../../src/essence/mmgisAPI/mmgisAPI' + +const LAYOUT_CHANGED_EVENT = 'mmgis-panel-layout-changed' /** * Create a basic mock panel configuration for testing @@ -34,31 +37,29 @@ export function createMockToolMetadata(overrides = {}) { } /** - * Mock window.dispatchEvent for testing layout changes + * Capture panel layout-changed notifications fired over the in-app event bus. + * + * PanelManager_ broadcasts layout changes via mmgisAPI.emit (mitt), not a + * window event. This subscribes to that bus and collects each notification, + * normalizing them to the shape the panel specs assert on: + * { type: 'mmgis-panel-layout-changed', detail: { panels } } */ -export function mockWindowDispatchEvent() { +export function mockLayoutChangedEvents() { const events = [] - // Ensure global window object exists - if (typeof global.window === 'undefined') { - global.window = {} + const handler = (payload) => { + events.push({ + type: LAYOUT_CHANGED_EVENT, + detail: payload, + }) } - const originalDispatchEvent = global.window.dispatchEvent - - global.window.dispatchEvent = (event) => { - events.push(event) - return true - } + mmgisAPI.on(LAYOUT_CHANGED_EVENT, handler) return { events, restore: () => { - if (originalDispatchEvent) { - global.window.dispatchEvent = originalDispatchEvent - } else { - delete global.window.dispatchEvent - } + mmgisAPI.off(LAYOUT_CHANGED_EVENT, handler) }, } } From 2efe83c58f38d33d18e913daf9c20ddaffe76688 Mon Sep 17 00:00:00 2001 From: Carson Davis Date: Wed, 17 Jun 2026 17:42:15 -0500 Subject: [PATCH 2/2] test(panelManager): auto-unsubscribe the layout-changed bus mock mockLayoutChangedEvents subscribed to the module-singleton mmgisAPI mitt bus and relied on the caller's restore() to unsubscribe. Register the teardown via vitest's onTestFinished so a spec that forgets restore() cannot leak the handler across tests. restore() is kept for explicit use. --- tests/unit/panelManager/testHelpers.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/panelManager/testHelpers.js b/tests/unit/panelManager/testHelpers.js index 5833a6d1d..f6554b0bb 100644 --- a/tests/unit/panelManager/testHelpers.js +++ b/tests/unit/panelManager/testHelpers.js @@ -1,6 +1,7 @@ import { PANEL_POSITION, PANEL_STATE, PANEL_LAYOUT_TYPE } from '../../../src/essence/Basics/PanelManager_/types/layout.ts' import { TOOL_ORIENTATION } from '../../../src/essence/Basics/ToolController_/types/tool.ts' import { mmgisAPI } from '../../../src/essence/mmgisAPI/mmgisAPI' +import { onTestFinished } from 'vitest' const LAYOUT_CHANGED_EVENT = 'mmgis-panel-layout-changed' @@ -55,6 +56,9 @@ export function mockLayoutChangedEvents() { } mmgisAPI.on(LAYOUT_CHANGED_EVENT, handler) + // Auto-unsubscribe when the current test ends, so a forgotten restore() + // cannot leak this handler onto the shared singleton bus. + onTestFinished(() => mmgisAPI.off(LAYOUT_CHANGED_EVENT, handler)) return { events,