test(ui-web): mock useTheme/useSkin in SimulatorBar.test.ts to fix vitest 4.x teardown RPC leak (#1884)
* test(ui-web): mock useTheme/useSkin in SimulatorBar.test.ts to fix vitest 4.x teardown leak
The two final tests ('should call switchTheme/switchSkin when … select changes')
mounted SimulatorBar with the real useTheme/useSkin composables. Both code paths
schedule console.warn output AFTER the synchronous test body resolves:
1. validateTokenContract() (src/shared/tokens/contract.ts) wraps work in
requestAnimationFrame and warns when CSS variables are missing. jsdom does
not resolve --color-* CSS variables, so the contract check logs ~24 warnings
via rAF on every theme/skin switch.
2. switchSkin() returns Promise<boolean>, but the @change handler discards the
promise and trigger('change') only awaits Vue's nextTick. The unawaited
loadStyles() dynamic import resolves later and may also call console.warn.
Vitest 4.x tightened teardown to fail rather than swallow pending RPC calls,
surfacing this latent bug as:
EnvironmentTeardownError: [vitest-worker]: Closing rpc while
"onUserConsoleLog" was pending
Reproducibly fails on Linux/Node 22 in CI; passes on macOS/Node 24 because rAF
+ dynamic-import timing differs.
Fix mirrors the existing precedent in tests/unit/router.test.ts: vi.mock both
composables at the top of the test file, returning shapes that match the real
contract (THEME_IDS / SKIN_IDS imported from ui-common rather than hand-rolled
subsets). The mocked switchSkin resolves immediately so no teardown leak occurs.
Also tightens the two affected tests to actually assert what their names claim
(switchTheme/switchSkin called with the selected value), instead of merely
checking the <select> exists.
* test(ui-web): use THEME_IDS/SKIN_IDS indices instead of hardcoded values
Address PR review: hardcoding 'dracula' and 'classic' as the target select
values would silently break the assertions if THEME_IDS or SKIN_IDS are
reordered or renamed in ui-common (the DOM $lt;select$gt; would keep its
current option, switchTheme/switchSkin would be called with that stale
value, and the toHaveBeenCalledWith assertion would compare against a
constant that no longer matches the dispatched event).
Pick targets that are guaranteed to differ from the mocked active values:
THEME_IDS[1] (≠ activeThemeId = THEME_IDS[0]) and the first SKIN_IDS entry
that is not 'modern' (≠ activeSkinId). The assertions then reference the
same constants, keeping the test coupled to the actual contract surface.
Note: the third reviewer comment about adding beforeEach(mockClear) is not
needed — vitest.config.ts already sets clearMocks: true and
restoreMocks: true, which run before each test.
* Revert "test(ui-web): use THEME_IDS/SKIN_IDS indices instead of hardcoded values"
This reverts commit
0941e5324d4501ca89094925654261ea1eda3fac.
* test(ui-web): add explicit afterEach mock cleanup in SimulatorBar test
Aligns SimulatorBar.test.ts with the dominant convention in ui/web's test
suite (Dialogs, StationCard, ConnectorRow, ClassicLayout, Actions, …),
which all add an explicit `afterEach(() => { vi.clearAllMocks() })`
alongside vitest.config.ts's global `clearMocks: true` / `restoreMocks: true`.
Belt-and-braces: the explicit reset keeps mock lifecycle visible next to
the mock declarations, even though it is technically redundant with the
global flags.