LayoutRoomPreviewerView.test.tsx 7 Ko
Télécharger le fichier
import { cleanup, fireEvent, render } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { LayoutRoomPreviewerView, resolvePreviewBackdropColor } from './LayoutRoomPreviewerView';
const previewMocks = vi.hoisted(() => ({
add: vi.fn(),
createRenderTexture: vi.fn((width: number, height: number) => ({ width, height, destroy: vi.fn() })),
getPixels: vi.fn((texture: { width: number; height: number }) => ({
pixels: new Uint8ClampedArray(texture.width * texture.height * 4),
width: texture.width,
height: texture.height
})),
remove: vi.fn(),
render: vi.fn()
}));
vi.mock('@octane/renderer', () => ({
GetConfiguration: () => ({ getValue: (_key: string, value: unknown) => value }),
GetRenderer: () => ({
render: previewMocks.render,
texture: { getPixels: previewMocks.getPixels }
}),
GetTicker: () => ({ add: previewMocks.add, remove: previewMocks.remove }),
OctaneLogger: { error: vi.fn() },
TextureUtils: { createRenderTexture: previewMocks.createRenderTexture }
}));
let resizeCallback: ResizeObserverCallback;
class ResizeObserverMock {
public constructor(callback: ResizeObserverCallback) {
resizeCallback = callback;
}
public observe = vi.fn();
public disconnect = vi.fn();
}
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
const context = {
createImageData: vi.fn((width: number, height: number) => ({
data: new Uint8ClampedArray(width * height * 4),
width,
height
})),
putImageData: vi.fn()
};
const createRoomPreviewer = () => {
const renderingCanvas = { canvasUpdated: true, master: {} };
return {
changeRoomObjectDirection: vi.fn(),
changeRoomObjectState: vi.fn(),
cycleAvatarAction: vi.fn(),
getPreviewCapabilities: () => ({ mode: 'avatar' }),
getRenderingCanvas: vi.fn(() => renderingCanvas),
getRoomCanvas: vi.fn(),
modifyRoomCanvas: vi.fn(),
updatePreviewRoomView: vi.fn()
} as any;
};
const renderPreview = (roomPreviewer: ReturnType<typeof createRoomPreviewer>, height = 240, initialWidth = 360) => {
const container = document.createElement('div');
let width = initialWidth;
Object.defineProperty(container, 'clientWidth', { configurable: true, get: () => width });
document.body.appendChild(container);
const view = render(<LayoutRoomPreviewerView height={height} roomPreviewer={roomPreviewer} />, { container });
return {
...view,
setWidth(nextWidth: number) {
width = nextWidth;
resizeCallback([], {} as ResizeObserver);
}
};
};
beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(context as never);
});
afterEach(() => {
cleanup();
vi.restoreAllMocks();
});
describe('LayoutRoomPreviewerView presentation canvas', () => {
it('recreates its render target only when the measured width changes', () => {
const roomPreviewer = createRoomPreviewer();
const view = renderPreview(roomPreviewer);
const firstTexture = previewMocks.createRenderTexture.mock.results[0].value;
view.setWidth(360);
expect(previewMocks.createRenderTexture).toHaveBeenCalledTimes(1);
expect(roomPreviewer.modifyRoomCanvas).not.toHaveBeenCalled();
view.setWidth(680);
expect(previewMocks.createRenderTexture).toHaveBeenLastCalledWith(680, 240);
expect(roomPreviewer.modifyRoomCanvas).toHaveBeenCalledOnce();
expect(roomPreviewer.modifyRoomCanvas).toHaveBeenCalledWith(680, 240);
expect(firstTexture.destroy).toHaveBeenCalledWith(true);
});
});
describe('LayoutRoomPreviewerView backdrop', () => {
it('clears the render target with the panel colour behind it so additive layers blend like AIR', () => {
const roomPreviewer = createRoomPreviewer();
const container = document.createElement('div');
container.style.backgroundColor = 'rgb(236, 234, 224)';
Object.defineProperty(container, 'clientWidth', { configurable: true, get: () => 200 });
document.body.appendChild(container);
render(<LayoutRoomPreviewerView height={120} roomPreviewer={roomPreviewer} />, { container });
expect(previewMocks.render).toHaveBeenLastCalledWith(expect.objectContaining({
clearColor: [236 / 255, 234 / 255, 224 / 255, 1]
}));
});
it('stays transparent over a backdrop image or translucent colour', () => {
const parent = document.createElement('div');
const child = document.createElement('div');
parent.appendChild(child);
document.body.appendChild(parent);
parent.style.backgroundColor = 'rgb(0, 0, 0)';
child.style.backgroundImage = 'url(stage.png)';
expect(resolvePreviewBackdropColor(child)).toBeNull();
child.style.backgroundImage = '';
child.style.backgroundColor = 'rgba(255, 255, 255, 0.5)';
expect(resolvePreviewBackdropColor(child)).toBeNull();
child.style.backgroundColor = '';
expect(resolvePreviewBackdropColor(child)).toEqual([0, 0, 0, 1]);
});
});
describe('LayoutRoomPreviewerView ticker', () => {
it('repositions on the animation clock but paints every updated frame', () => {
const roomPreviewer = createRoomPreviewer();
renderPreview(roomPreviewer);
const update = previewMocks.add.mock.calls[0][0] as (ticker: { deltaMS: number }) => void;
vi.clearAllMocks();
for (let frame = 0; frame < 5; frame++) update({ deltaMS: 16 });
expect(roomPreviewer.updatePreviewRoomView).toHaveBeenCalledTimes(2);
expect(context.putImageData).toHaveBeenCalledTimes(5);
});
});
describe('LayoutRoomPreviewerView interactions', () => {
it('keeps furniture state on a single click', () => {
const roomPreviewer = createRoomPreviewer();
const view = renderPreview(roomPreviewer, 200);
fireEvent.click(view.container.querySelector('.shadow-room-previewer')!);
expect(roomPreviewer.changeRoomObjectState).toHaveBeenCalledOnce();
});
it('keeps preview clicks dedicated to furniture state even with modifier keys', () => {
const roomPreviewer = createRoomPreviewer();
const view = renderPreview(roomPreviewer, 200);
fireEvent.click(view.container.querySelector('.shadow-room-previewer')!, { shiftKey: true });
expect(roomPreviewer.changeRoomObjectDirection).not.toHaveBeenCalled();
expect(roomPreviewer.changeRoomObjectState).toHaveBeenCalledOnce();
});
it('does not cycle avatar actions from the preview surface', () => {
const roomPreviewer = createRoomPreviewer();
const view = renderPreview(roomPreviewer, 200);
fireEvent.click(view.container.querySelector('.shadow-room-previewer')!);
expect(roomPreviewer.cycleAvatarAction).not.toHaveBeenCalled();
expect(roomPreviewer.changeRoomObjectState).toHaveBeenCalledOnce();
});
});
