5180 fichiers 38.5 Mo 0 téléchargements 313 vues mis à jour il y a 6 heures ui
nitro-v36-with-renderer-v36 / src / hooks / rooms / widgets / useFloorplanLiveSync.test.tsx
useFloorplanLiveSync.test.tsx 7.1 Ko Télécharger le fichier
/* @vitest-environment jsdom */

import { act, cleanup, renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { FloorplanState } from '../../../components/floorplan-editor/state/types';
import { buildLivePreviewPayload, LivePreviewPayload, livePreviewPayloadsEqual, useFloorplanLiveSync } from './useFloorplanLiveSync';

// Spy on the renderer hook seam — we hand back a manager that
// records calls to applyFloorModelLocally so we can assert what
// the hook pushed to the room.
const applyMock = vi.fn<(model: string, wallHeight: number, scale: boolean) => boolean>();
const thicknessMock = vi.fn<(roomId: number, wall: number, floor: number) => boolean>();

vi.mock('@octane/renderer', async (importOriginal) => {
    const actual = await importOriginal<typeof import('@octane/renderer')>();

    return {
        ...actual,
        GetRoomMessageHandler: () => ({
            applyFloorModelLocally: (model: string, wallHeight: number, scale: boolean) => applyMock(model, wallHeight, scale)
        }),
        GetRoomEngine: () => ({
            updateRoomInstancePlaneThickness: (roomId: number, wall: number, floor: number) => thicknessMock(roomId, wall, floor)
        })
    };
});

// Pin the active room id so the engine.updateRoomInstancePlaneThickness
// branch is exercised. -1 is the "no active room" sentinel and skips
// the thickness call.
vi.mock('../../session/useSessionSnapshots', () => ({
    useActiveRoomSessionSnapshot: () => ({ roomId: 42 })
}));

const baseState: FloorplanState = {
    tiles: [
        [
            { h: 0, blocked: false },
            { h: 0, blocked: false }
        ],
        [
            { h: 0, blocked: false },
            { h: 0, blocked: false }
        ]
    ],
    door: { x: 1, y: 1, dir: 2 },
    thickness: { wall: 1, floor: 1 },
    wallHeight: 1,
    brush: { h: 0, action: 'SET' },
    selection: new Set(),
    squareSelect: false,
    pickUpBlocking: false,
    lease: { holder: null, me: false, expiresAt: null },
    seq: 0
};

const samePayload = (s: FloorplanState): LivePreviewPayload => buildLivePreviewPayload(s);

describe('useFloorplanLiveSync', () => {
    beforeEach(() => {
        applyMock.mockReset().mockReturnValue(true);
        thicknessMock.mockReset().mockReturnValue(true);
    });

    afterEach(() => {
        cleanup();
    });

    it('does not call the renderer before a baseline is set', () => {
        renderHook(() => useFloorplanLiveSync({ enabled: true, state: baseState }));

        expect(applyMock).not.toHaveBeenCalled();
    });

    it('does not call the renderer when the state equals the baseline', () => {
        const { result } = renderHook(() => useFloorplanLiveSync({ enabled: true, state: baseState }));

        act(() => {
            result.current.setBaseline(samePayload(baseState));
        });

        expect(applyMock).not.toHaveBeenCalled();
    });

    it('applies the new floor model locally on every diverging state change', () => {
        const { result, rerender } = renderHook(({ state }: { state: FloorplanState }) => useFloorplanLiveSync({ enabled: true, state }), {
            initialProps: { state: baseState }
        });

        act(() => {
            result.current.setBaseline(samePayload(baseState));
        });

        const next: FloorplanState = {
            ...baseState,
            tiles: [
                [
                    { h: 1, blocked: false },
                    { h: 0, blocked: false }
                ],
                [
                    { h: 0, blocked: false },
                    { h: 0, blocked: false }
                ]
            ]
        };

        rerender({ state: next });

        expect(applyMock).toHaveBeenCalledTimes(1);
        // The renderer parser takes server-space wallHeight (editor - 1).
        expect(applyMock.mock.calls[0][1]).toBe(0);
        expect(applyMock.mock.calls[0][2]).toBe(true);
        // Same brush/selection change should NOT push another paint.
        rerender({ state: { ...next, selection: new Set(['0,0']) } });
        expect(applyMock).toHaveBeenCalledTimes(1);
    });

    it('mirrors the thickness slider into the renderer engine', () => {
        const { result, rerender } = renderHook(({ state }: { state: FloorplanState }) => useFloorplanLiveSync({ enabled: true, state }), {
            initialProps: { state: baseState }
        });

        act(() => {
            result.current.setBaseline(samePayload(baseState));
        });

        rerender({ state: { ...baseState, thickness: { wall: 3, floor: 2 } } });

        // Thickness 3 -> 2.0, thickness 2 -> 1.0
        const [roomId, wall, floor] = thicknessMock.mock.calls[thicknessMock.mock.calls.length - 1];
        expect(roomId).toBe(42);
        expect(wall).toBe(2);
        expect(floor).toBe(1);
    });

    it('does not call the renderer when disabled', () => {
        const { result, rerender } = renderHook(({ enabled, state }: { enabled: boolean; state: FloorplanState }) => useFloorplanLiveSync({ enabled, state }), {
            initialProps: { enabled: false, state: baseState }
        });

        act(() => {
            result.current.setBaseline(samePayload(baseState));
        });

        rerender({ enabled: false, state: { ...baseState, wallHeight: 5 } });

        expect(applyMock).not.toHaveBeenCalled();
    });

    it('revert re-applies the baseline to the renderer', () => {
        const { result, rerender } = renderHook(({ state }: { state: FloorplanState }) => useFloorplanLiveSync({ enabled: true, state }), {
            initialProps: { state: baseState }
        });

        act(() => {
            result.current.setBaseline(samePayload(baseState));
        });

        rerender({ state: { ...baseState, wallHeight: 7 } });

        // One push from the wallHeight bump.
        expect(applyMock).toHaveBeenCalledTimes(1);

        act(() => {
            result.current.revert();
        });

        expect(applyMock).toHaveBeenCalledTimes(2);
        // Revert applies the baseline payload, which has wallHeight=1
        // → server-space 0.
        expect(applyMock.mock.calls[1][1]).toBe(0);
    });

    it('revert is a no-op when no baseline has been recorded', () => {
        const { result } = renderHook(() => useFloorplanLiveSync({ enabled: true, state: baseState }));

        act(() => {
            result.current.revert();
        });

        expect(applyMock).not.toHaveBeenCalled();
    });
});

describe('livePreviewPayloadsEqual', () => {
    const p: LivePreviewPayload = {
        tilemap: 'xx\rxx',
        doorX: 0,
        doorY: 0,
        doorDir: 2,
        thicknessWall: 1,
        thicknessFloor: 1,
        wallHeight: 3
    };

    it('returns true for identical payloads', () => {
        expect(livePreviewPayloadsEqual(p, { ...p })).toBe(true);
    });

    it('detects a tilemap diff', () => {
        expect(livePreviewPayloadsEqual(p, { ...p, tilemap: '00\r00' })).toBe(false);
    });

    it('detects a wallHeight diff', () => {
        expect(livePreviewPayloadsEqual(p, { ...p, wallHeight: 4 })).toBe(false);
    });

    it('detects a door diff', () => {
        expect(livePreviewPayloadsEqual(p, { ...p, doorDir: 4 })).toBe(false);
    });
});
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser