feat(navigator): extract useDoorState (TDD) – Task 2

- Add `src/hooks/rooms/widgets/useDoorState.ts`: useBetween-based
  singleton wrapping DoorbellMessageEvent / RoomDoorbellAcceptedEvent /
  FlatAccessDeniedMessageEvent / GenericErrorEvent /
  GetGuestRoomResultEvent; all 5 handlers wrapped in useCallback([])
  so their references are stable across useBetween tick() calls and
  the effect dep-array never triggers re-registration.
- Add `src/hooks/rooms/widgets/useDoorState.test.tsx`: 11-case Vitest
  suite (initial state, 5 event transitions, 2 no-op guards,
  GetGuestRoomResultEvent doorbell/password paths, reset()).
- Extend `src/nitro-renderer.mock.ts`: new MessageEvent base class with
  callBack/type/getParser; DoorbellMessageEvent / RoomDoorbellAcceptedEvent /
  FlatAccessDeniedMessageEvent / GenericErrorEvent / GetGuestRoomResultEvent
  concrete stubs; RoomDataParser.DOORBELL_STATE + PASSWORD_STATE; separate
  msgListeners map (cleared independently of NitroEvent listeners so
  useBetween subscriptions survive between test cases); WeakMap wrapper
  for correct removeMessageEvent; GetCommunication routes to msgListeners.

All 11 useDoorState tests pass; full suite 453/456 (3 pre-existing
FloorplanCanvasSVG jsdom/SVG-CTM failures unrelated to this task).
This commit is contained in:
simoleo89
2026-05-26 21:35:30 +02:00
committed by simoleo89
parent 1f0cf88344
commit 07bbc0c78d
3 changed files with 330 additions and 7 deletions
+77
View File
@@ -0,0 +1,77 @@
import { DoorbellMessageEvent, FlatAccessDeniedMessageEvent,
GenericErrorEvent, GetGuestRoomResultEvent,
GetSessionDataManager, RoomDataParser,
RoomDoorbellAcceptedEvent } from '@nitrots/nitro-renderer';
import { useCallback, useState } from 'react';
import { useBetween } from 'use-between';
import { DoorStateType } from '../../../api';
import { useMessageEvent } from '../../events';
export type DoorStateSnapshot = {
roomInfo: RoomDataParser | null;
state: number;
};
const INITIAL: DoorStateSnapshot = { roomInfo: null, state: DoorStateType.NONE };
const useDoorStateStore = () =>
{
const [ snapshot, setSnapshot ] = useState<DoorStateSnapshot>(INITIAL);
const handleDoorbell = useCallback((event: DoorbellMessageEvent) =>
{
const parser = event.getParser();
if(parser.userName && parser.userName.length > 0) return;
setSnapshot(prev => ({ ...prev, state: DoorStateType.STATE_WAITING }));
}, []);
const handleAccepted = useCallback((event: RoomDoorbellAcceptedEvent) =>
{
const parser = event.getParser();
if(parser.userName && parser.userName.length > 0) return;
setSnapshot(prev => ({ ...prev, state: DoorStateType.STATE_ACCEPTED }));
}, []);
const handleDenied = useCallback((event: FlatAccessDeniedMessageEvent) =>
{
const parser = event.getParser();
if(parser.userName && parser.userName.length > 0) return;
setSnapshot(prev => ({ ...prev, state: DoorStateType.STATE_NO_ANSWER }));
}, []);
const handleGenericError = useCallback((event: GenericErrorEvent) =>
{
const parser = event.getParser();
if(parser.errorCode !== -100002) return;
setSnapshot(prev => ({ ...prev, state: DoorStateType.STATE_WRONG_PASSWORD }));
}, []);
const handleGuestRoom = useCallback((event: GetGuestRoomResultEvent) =>
{
const parser = event.getParser();
if(!parser.roomForward) return;
if(parser.data.ownerName === GetSessionDataManager().userName) return;
if(parser.isGroupMember) return;
if(parser.data.doorMode === RoomDataParser.DOORBELL_STATE)
{
setSnapshot({ roomInfo: parser.data, state: DoorStateType.START_DOORBELL });
return;
}
if(parser.data.doorMode === RoomDataParser.PASSWORD_STATE)
{
setSnapshot({ roomInfo: parser.data, state: DoorStateType.START_PASSWORD });
}
}, []);
useMessageEvent<DoorbellMessageEvent>(DoorbellMessageEvent, handleDoorbell);
useMessageEvent<RoomDoorbellAcceptedEvent>(RoomDoorbellAcceptedEvent, handleAccepted);
useMessageEvent<FlatAccessDeniedMessageEvent>(FlatAccessDeniedMessageEvent, handleDenied);
useMessageEvent<GenericErrorEvent>(GenericErrorEvent, handleGenericError);
useMessageEvent<GetGuestRoomResultEvent>(GetGuestRoomResultEvent, handleGuestRoom);
const reset = useCallback(() => setSnapshot(INITIAL), []);
return { snapshot, setSnapshot, reset };
};
export const useDoorState = () => useBetween(useDoorStateStore);