diff --git a/packages/jsx/src/render.ts b/packages/jsx/src/render.ts index 0574e79c..a2c871be 100644 --- a/packages/jsx/src/render.ts +++ b/packages/jsx/src/render.ts @@ -15,13 +15,17 @@ import { createElement } from './createElement.js'; import { setCurrentApp } from './runtime.js'; /** - * Shared inline unmount helper — used by both the dev-server-backed path - * and the fallback path so cleanup logic stays synchronized. + * Unmount a list of apps. Swallow any errors thrown by `unmount()` but log + * a single error message for observability. Exported only for tests. */ -function _unmountApps(apps: Array<{ unmount?: () => void }>): void { +export function unmountApps(apps: Array<{ unmount?: () => void }>): void { apps.forEach((app) => { if (typeof app.unmount === 'function') { - try { app.unmount(); } catch {} + try { + app.unmount(); + } catch (err) { + console.error('[jsx] Error during unmount():', err); + } } }); } @@ -162,7 +166,7 @@ export async function render( // dev-server unavailable — use local helper for cleanup const apps = (globalThis as any).__termuijs_apps; if (Array.isArray(apps)) { - _unmountApps(apps); + unmountApps(apps); (globalThis as any).__termuijs_apps = []; } }); diff --git a/packages/jsx/src/unmountHelpers.test.ts b/packages/jsx/src/unmountHelpers.test.ts new file mode 100644 index 00000000..c0d848ab --- /dev/null +++ b/packages/jsx/src/unmountHelpers.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect, vi } from 'vitest'; +import { unmountApps } from './render.js'; + +describe('unmountApps', () => { + it('logs errors, completes cleanup, and does not rethrow', () => { + const err = new Error('boom'); + const throwing = { unmount: vi.fn(() => { throw err; }) }; + const called = { unmount: vi.fn(() => { /* ok */ }) }; + + const spy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + try { + // Should not throw + expect(() => unmountApps([throwing, called])).not.toThrow(); + + // Ensure the second unmount still ran + expect(called.unmount).toHaveBeenCalledTimes(1); + + // Ensure we logged the error exactly once with the expected message + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('[jsx] Error during unmount():', err); + } finally { + spy.mockRestore(); + } + }); +});