mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
refactor(hooks/rooms): collapse usePetPackageWidget 5 useStates into useReducer
The hook tracked five related useState fields driving the pet-package
naming dialog (isVisible / objectId / objectType / petName / errorResult).
They transitioned in lockstep on the two RoomSessionPetPackageEvent
types and the inline change handler — textbook state-machine territory.
Collapse into a single useReducer with four explicit transitions:
- 'open' → REQUESTED event lands; flips visible, records target
- 'close' → REQUESTED-result success OR user dismiss; resets to INITIAL
- 'set-name' → input change; updates petName AND clears any error
(the previous code had this side effect inlined in
onChangePetName as `if(errorResult.length) setErrorResult('')`,
now it's part of the reducer contract)
- 'set-error' → REQUESTED-result with validation failure; sets the label
Plus extract `getPetPackageNameError(code)` to a top-level exported
pure function (was an inline closure named getErrorResultForCode).
The mapping is server-protocol contract, not UI state — moving it out
of the hook means it's testable, reusable, and won't be recreated on
every render.
Public API of usePetPackageWidget is unchanged — the one consumer
(PetPackageWidgetView) reads the same destructured fields. Verified
via grep.
Tests: 4 new cases on getPetPackageNameError covering code 0 / 1-4 /
falsy / unknown-fallback. Suite: 207/207 (was 203/203).
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('../../../api', () => ({
|
||||
LocalizeText: (key: string) => key,
|
||||
SendMessageComposer: vi.fn()
|
||||
}));
|
||||
|
||||
import { getPetPackageNameError } from './usePetPackageWidget';
|
||||
|
||||
describe('getPetPackageNameError', () =>
|
||||
{
|
||||
it('returns empty string for code 0 (no error)', () =>
|
||||
{
|
||||
expect(getPetPackageNameError(0)).toBe('');
|
||||
});
|
||||
|
||||
it('returns empty string for falsy values (null/undefined coerced)', () =>
|
||||
{
|
||||
expect(getPetPackageNameError(null as never)).toBe('');
|
||||
expect(getPetPackageNameError(undefined as never)).toBe('');
|
||||
});
|
||||
|
||||
it('maps the four documented error codes to their localization keys', () =>
|
||||
{
|
||||
expect(getPetPackageNameError(1)).toBe('catalog.alert.petname.long');
|
||||
expect(getPetPackageNameError(2)).toBe('catalog.alert.petname.short');
|
||||
expect(getPetPackageNameError(3)).toBe('catalog.alert.petname.chars');
|
||||
expect(getPetPackageNameError(4)).toBe('catalog.alert.petname.bobba');
|
||||
});
|
||||
|
||||
it('falls back to the bobba label for unknown error codes (defensive default)', () =>
|
||||
{
|
||||
expect(getPetPackageNameError(99)).toBe('catalog.alert.petname.bobba');
|
||||
expect(getPetPackageNameError(-1)).toBe('catalog.alert.petname.bobba');
|
||||
});
|
||||
});
|
||||
@@ -1,75 +1,106 @@
|
||||
import { GetRoomEngine, OpenPetPackageMessageComposer, RoomObjectCategory, RoomSessionPetPackageEvent } from '@nitrots/nitro-renderer';
|
||||
import { useState } from 'react';
|
||||
import { useReducer } from 'react';
|
||||
import { LocalizeText, SendMessageComposer } from '../../../api';
|
||||
import { useNitroEvent } from '../../events';
|
||||
|
||||
const usePetPackageWidgetState = () =>
|
||||
interface PetPackageState
|
||||
{
|
||||
const [ isVisible, setIsVisible ] = useState<boolean>(false);
|
||||
const [ objectId, setObjectId ] = useState<number>(-1);
|
||||
const [ objectType, setObjectType ] = useState<string>('');
|
||||
const [ petName, setPetName ] = useState<string>('');
|
||||
const [ errorResult, setErrorResult ] = useState<string>('');
|
||||
isVisible: boolean;
|
||||
objectId: number;
|
||||
objectType: string;
|
||||
petName: string;
|
||||
errorResult: string;
|
||||
}
|
||||
|
||||
const onClose = () =>
|
||||
{
|
||||
setErrorResult('');
|
||||
setPetName('');
|
||||
setObjectType('');
|
||||
setObjectId(-1);
|
||||
setIsVisible(false);
|
||||
type PetPackageAction =
|
||||
| { type: 'open'; objectId: number; objectType: string }
|
||||
| { type: 'close' }
|
||||
| { type: 'set-name'; petName: string }
|
||||
| { type: 'set-error'; errorResult: string };
|
||||
|
||||
const INITIAL_STATE: PetPackageState = {
|
||||
isVisible: false,
|
||||
objectId: -1,
|
||||
objectType: '',
|
||||
petName: '',
|
||||
errorResult: ''
|
||||
};
|
||||
|
||||
const onConfirm = () =>
|
||||
const petPackageReducer = (state: PetPackageState, action: PetPackageAction): PetPackageState =>
|
||||
{
|
||||
SendMessageComposer(new OpenPetPackageMessageComposer(objectId, petName));
|
||||
switch(action.type)
|
||||
{
|
||||
case 'open':
|
||||
return { ...INITIAL_STATE, isVisible: true, objectId: action.objectId, objectType: action.objectType };
|
||||
case 'close':
|
||||
return INITIAL_STATE;
|
||||
case 'set-name':
|
||||
// Typing into the input always clears any previous error label.
|
||||
return { ...state, petName: action.petName, errorResult: '' };
|
||||
case 'set-error':
|
||||
return { ...state, errorResult: action.errorResult };
|
||||
}
|
||||
};
|
||||
|
||||
const onChangePetName = (petName: string) =>
|
||||
/**
|
||||
* Maps the pet-package name-validation error code returned by the
|
||||
* server to a localized error label. Exported for testability — the
|
||||
* mapping is server-protocol contract, not UI state.
|
||||
*/
|
||||
export const getPetPackageNameError = (errorCode: number): string =>
|
||||
{
|
||||
setPetName(petName);
|
||||
if(errorResult.length > 0) setErrorResult('');
|
||||
};
|
||||
|
||||
const getErrorResultForCode = (errorCode: number) =>
|
||||
{
|
||||
if(!errorCode || errorCode === 0) return;
|
||||
if(!errorCode) return '';
|
||||
|
||||
switch(errorCode)
|
||||
{
|
||||
case 1:
|
||||
return LocalizeText('catalog.alert.petname.long');
|
||||
case 2:
|
||||
return LocalizeText('catalog.alert.petname.short');
|
||||
case 3:
|
||||
return LocalizeText('catalog.alert.petname.chars');
|
||||
case 1: return LocalizeText('catalog.alert.petname.long');
|
||||
case 2: return LocalizeText('catalog.alert.petname.short');
|
||||
case 3: return LocalizeText('catalog.alert.petname.chars');
|
||||
case 4:
|
||||
default:
|
||||
return LocalizeText('catalog.alert.petname.bobba');
|
||||
}
|
||||
};
|
||||
|
||||
const usePetPackageWidgetState = () =>
|
||||
{
|
||||
const [ state, dispatch ] = useReducer(petPackageReducer, INITIAL_STATE);
|
||||
|
||||
const onClose = () => dispatch({ type: 'close' });
|
||||
const onConfirm = () => SendMessageComposer(new OpenPetPackageMessageComposer(state.objectId, state.petName));
|
||||
const onChangePetName = (petName: string) => dispatch({ type: 'set-name', petName });
|
||||
|
||||
useNitroEvent<RoomSessionPetPackageEvent>(RoomSessionPetPackageEvent.RSOPPE_OPEN_PET_PACKAGE_REQUESTED, event =>
|
||||
{
|
||||
if(!event) return;
|
||||
|
||||
const roomObject = GetRoomEngine().getRoomObject(event.session.roomId, event.objectId, RoomObjectCategory.FLOOR);
|
||||
|
||||
setObjectId(event.objectId);
|
||||
setObjectType(roomObject.type);
|
||||
setIsVisible(true);
|
||||
dispatch({ type: 'open', objectId: event.objectId, objectType: roomObject.type });
|
||||
});
|
||||
|
||||
useNitroEvent<RoomSessionPetPackageEvent>(RoomSessionPetPackageEvent.RSOPPE_OPEN_PET_PACKAGE_RESULT, event =>
|
||||
{
|
||||
if(!event) return;
|
||||
|
||||
if(event.nameValidationStatus === 0) onClose();
|
||||
if(event.nameValidationStatus === 0)
|
||||
{
|
||||
dispatch({ type: 'close' });
|
||||
return;
|
||||
}
|
||||
|
||||
if(event.nameValidationStatus !== 0) setErrorResult(getErrorResultForCode(event.nameValidationStatus));
|
||||
dispatch({ type: 'set-error', errorResult: getPetPackageNameError(event.nameValidationStatus) });
|
||||
});
|
||||
|
||||
return { isVisible, errorResult, petName, objectType, onChangePetName, onConfirm, onClose };
|
||||
return {
|
||||
isVisible: state.isVisible,
|
||||
errorResult: state.errorResult,
|
||||
petName: state.petName,
|
||||
objectType: state.objectType,
|
||||
onChangePetName,
|
||||
onConfirm,
|
||||
onClose
|
||||
};
|
||||
};
|
||||
|
||||
export const usePetPackageWidget = usePetPackageWidgetState;
|
||||
|
||||
Reference in New Issue
Block a user