/** @vitest-environment jsdom */ import { fireEvent, render, screen } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { createGameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp'; import ProjectDevelopmentView from '../src/view/project-development'; const PREVIEW_URL = 'http://127.0.0.1:4173/'; function installInvoke() { window.__TAURI__ = { core: { invoke: vi.fn(async (command: string, args?: Record) => { if (command === 'read_local_project_resource_graph') { return { resourceIds: [], referenceEdges: [], taskFlows: [], categories: [], diagnostics: [], }; } if (command === 'read_local_project_resource_canvas_layout') { return { schemaVersion: 'game-creator-resource-layout.v1', projectId: args?.expectedProjectId, mode: args?.mode, revision: 0, positions: [], updatedAt: 0, }; } throw new Error(`unexpected invoke ${command}`); }), }, } as unknown as typeof window.__TAURI__; } function renderRunView() { installInvoke(); const manifest = createGameCreationAppManifest( 'run-preview-fullscreen', '运行页全屏预览', ); render( 项目总控} onHomeOpen={vi.fn()} onProjectsOpen={vi.fn()} onNotice={vi.fn()} />, ); } /** * jsdom 完全没有 Fullscreen API(`document.fullscreenEnabled` / `requestFullscreen` / * `fullscreenElement` 都是 undefined),所以这一组用例必须自己把宿主那一份补出来: * 全屏状态、元素身份与 `fullscreenchange` 都按规范最小实现,只用于验按钮的真实行为。 */ function installFullscreenHost() { let fullscreenElement: Element | null = null; const requestFullscreen = vi.fn(function (this: Element) { // 这枚桩就是要记录调用方传进来的 `this`:全屏元素身份正是本用例的断言对象(画面那一格)。 // eslint-disable-next-line @typescript-eslint/no-this-alias fullscreenElement = this; document.dispatchEvent(new Event('fullscreenchange')); return Promise.resolve(); }); const exitFullscreen = vi.fn(() => { fullscreenElement = null; document.dispatchEvent(new Event('fullscreenchange')); return Promise.resolve(); }); Object.defineProperty(document, 'fullscreenElement', { configurable: true, get: () => fullscreenElement, }); Object.defineProperty(document, 'exitFullscreen', { configurable: true, value: exitFullscreen, }); Object.defineProperty(Element.prototype, 'requestFullscreen', { configurable: true, writable: true, value: requestFullscreen, }); return { requestFullscreen, exitFullscreen }; } afterEach(() => { document.body.innerHTML = ''; Reflect.deleteProperty(document, 'fullscreenElement'); Reflect.deleteProperty(document, 'exitFullscreen'); Reflect.deleteProperty(Element.prototype, 'requestFullscreen'); vi.clearAllMocks(); vi.restoreAllMocks(); }); describe('运行页「全屏预览」', () => { it('点画面右下角那枚按钮就把画面那一格送进全屏,再点退出', async () => { const { requestFullscreen, exitFullscreen } = installFullscreenHost(); renderRunView(); const button = await screen.findByRole('button', { name: '全屏预览' }); // 按钮住在画面那一格里(右下角由样式给),全屏的也是那一格——不是整块工作台。 const stage = document.querySelector('.game-run-preview'); expect(stage).not.toBeNull(); expect(button.closest('.game-run-preview')).toBe(stage); expect(button.getAttribute('aria-pressed')).toBe('false'); fireEvent.click(button); expect(requestFullscreen).toHaveBeenCalledTimes(1); // 全屏元素就是画面那一格:按钮自己也算得出来(状态来自 fullscreenchange,不是乐观值)。 expect(document.fullscreenElement).toBe(stage); const exitButton = await screen.findByRole('button', { name: '退出全屏预览', }); expect(exitButton.getAttribute('aria-pressed')).toBe('true'); fireEvent.click(exitButton); expect(exitFullscreen).toHaveBeenCalledTimes(1); expect( (await screen.findByRole('button', { name: '全屏预览' })).getAttribute( 'aria-pressed', ), ).toBe('false'); }); it('宿主没有 Fullscreen API 时不渲染这枚按钮,而不是留一个点了没反应的入口', async () => { renderRunView(); await screen.findByTitle('运行页全屏预览 游戏运行画面'); expect(screen.queryByRole('button', { name: '全屏预览' })).toBeNull(); }); });