/* @vitest-environment jsdom */
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ApiClientError } from '../../services/apiClient';
import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome';
const renameEditorProjectMock = vi.hoisted(() => vi.fn());
vi.mock('../../services/image-editor/editorProjectClient', async () => {
const actual = await vi.importActual<
typeof import('../../services/image-editor/editorProjectClient')
>('../../services/image-editor/editorProjectClient');
return {
...actual,
renameEditorProject: renameEditorProjectMock,
};
});
function ChromeHarness({
openEditorLoginModal = vi.fn(),
}: {
openEditorLoginModal?: (postLoginAction?: (() => void) | null) => void;
}) {
const chrome = useImageCanvasEditorChrome({ openEditorLoginModal });
return (
{chrome.projectTitle}
{chrome.projectRenameValue}
{String(chrome.isRenamingProject)}
{String(chrome.isProjectRenameSaving)}
{chrome.projectRenameError ?? '-'}
{chrome.activeSidebarPanel ?? '-'}
{chrome.activeTool}
{String(chrome.isZoomMenuOpen)}
{String(chrome.isBackgroundSettingsOpen)}
{String(chrome.isMinimapOpen)}
{chrome.canvasBackgroundColor}
{chrome.canvasBackgroundHexValue}
);
}
describe('useImageCanvasEditorChrome', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renames projects and resets rename state from the saved title', async () => {
renameEditorProjectMock.mockResolvedValueOnce({
projectId: 'project-1',
title: '后端项目名',
viewport: { x: 0, y: 0, scale: 1 },
layers: [],
resources: [],
updatedAt: '2026-06-17T00:00:00.000Z',
});
render();
fireEvent.click(screen.getByRole('button', { name: 'set title' }));
fireEvent.click(screen.getByRole('button', { name: 'start rename' }));
expect(screen.getByTestId('rename-value').textContent).toBe('已有项目');
fireEvent.click(screen.getByRole('button', { name: 'set rename' }));
fireEvent.click(screen.getByRole('button', { name: 'submit rename' }));
expect(screen.getByTestId('saving').textContent).toBe('true');
await waitFor(() => {
expect(renameEditorProjectMock).toHaveBeenCalledWith(
'project-1',
'新项目',
);
});
expect(screen.getByTestId('title').textContent).toBe('后端项目名');
expect(screen.getByTestId('rename-value').textContent).toBe('后端项目名');
expect(screen.getByTestId('renaming').textContent).toBe('false');
expect(screen.getByTestId('saving').textContent).toBe('false');
});
it('validates rename input and opens login on rename auth errors', async () => {
const openEditorLoginModal = vi.fn();
renameEditorProjectMock.mockRejectedValueOnce(
new ApiClientError({
message: '未授权访问',
status: 401,
code: 'UNAUTHORIZED',
}),
);
render();
fireEvent.click(screen.getByRole('button', { name: 'blank rename' }));
fireEvent.click(screen.getByRole('button', { name: 'submit rename' }));
expect(screen.getByTestId('rename-error').textContent).toBe(
'项目名称不能为空',
);
expect(renameEditorProjectMock).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: 'set rename' }));
fireEvent.click(screen.getByRole('button', { name: 'submit rename' }));
await waitFor(() => {
expect(openEditorLoginModal).toHaveBeenCalledTimes(1);
});
expect(screen.getByTestId('rename-error').textContent).toBe('未授权访问');
});
it('manages background colors and chrome panel toggles', () => {
render();
fireEvent.click(screen.getByRole('button', { name: 'apply short hex' }));
expect(screen.getByTestId('background-color').textContent).toBe('#aabbcc');
expect(screen.getByTestId('background-hex').textContent).toBe('#aabbcc');
fireEvent.click(screen.getByRole('button', { name: 'invalid hex' }));
expect(screen.getByTestId('background-color').textContent).toBe('#aabbcc');
expect(screen.getByTestId('background-hex').textContent).toBe(
'#not-a-color',
);
fireEvent.click(screen.getByRole('button', { name: 'valid hex' }));
expect(screen.getByTestId('background-color').textContent).toBe('#ffffff');
expect(screen.getByTestId('background-hex').textContent).toBe('#ffffff');
fireEvent.click(screen.getByRole('button', { name: 'toggle assets' }));
expect(screen.getByTestId('sidebar').textContent).toBe('-');
fireEvent.click(screen.getByRole('button', { name: 'toggle layers' }));
expect(screen.getByTestId('sidebar').textContent).toBe('layers');
fireEvent.click(screen.getByRole('button', { name: 'toggle zoom' }));
fireEvent.click(screen.getByRole('button', { name: 'toggle background' }));
fireEvent.click(screen.getByRole('button', { name: 'toggle minimap' }));
fireEvent.click(screen.getByRole('button', { name: 'set hand' }));
expect(screen.getByTestId('zoom').textContent).toBe('true');
expect(screen.getByTestId('background-open').textContent).toBe('true');
expect(screen.getByTestId('minimap').textContent).toBe('false');
expect(screen.getByTestId('tool').textContent).toBe('hand');
act(() => {
screen.getByRole('button', { name: 'close panels' }).click();
});
expect(screen.getByTestId('sidebar').textContent).toBe('-');
expect(screen.getByTestId('zoom').textContent).toBe('false');
expect(screen.getByTestId('background-open').textContent).toBe('false');
});
});