225 lines
8.2 KiB
TypeScript
225 lines
8.2 KiB
TypeScript
/* @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 (
|
|
<div>
|
|
<span data-testid="title">{chrome.projectTitle}</span>
|
|
<span data-testid="rename-value">{chrome.projectRenameValue}</span>
|
|
<span data-testid="renaming">{String(chrome.isRenamingProject)}</span>
|
|
<span data-testid="saving">{String(chrome.isProjectRenameSaving)}</span>
|
|
<span data-testid="rename-error">{chrome.projectRenameError ?? '-'}</span>
|
|
<span data-testid="sidebar">{chrome.activeSidebarPanel ?? '-'}</span>
|
|
<span data-testid="tool">{chrome.activeTool}</span>
|
|
<span data-testid="zoom">{String(chrome.isZoomMenuOpen)}</span>
|
|
<span data-testid="background-open">
|
|
{String(chrome.isBackgroundSettingsOpen)}
|
|
</span>
|
|
<span data-testid="minimap">{String(chrome.isMinimapOpen)}</span>
|
|
<span data-testid="background-color">
|
|
{chrome.canvasBackgroundColor}
|
|
</span>
|
|
<span data-testid="background-hex">
|
|
{chrome.canvasBackgroundHexValue}
|
|
</span>
|
|
<button type="button" onClick={chrome.startProjectRename}>
|
|
start rename
|
|
</button>
|
|
<button type="button" onClick={chrome.cancelProjectRename}>
|
|
cancel rename
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.submitProjectRename('project-1')}
|
|
>
|
|
submit rename
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.submitProjectRename(null)}
|
|
>
|
|
submit without project
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.setProjectRenameValue(' 新项目 ')}
|
|
>
|
|
set rename
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.setProjectRenameValue(' ')}
|
|
>
|
|
blank rename
|
|
</button>
|
|
<button type="button" onClick={() => chrome.setProjectTitle('已有项目')}>
|
|
set title
|
|
</button>
|
|
<button type="button" onClick={() => chrome.setActiveTool('hand')}>
|
|
set hand
|
|
</button>
|
|
<button type="button" onClick={() => chrome.toggleSidebarPanel('assets')}>
|
|
toggle assets
|
|
</button>
|
|
<button type="button" onClick={() => chrome.toggleSidebarPanel('layers')}>
|
|
toggle layers
|
|
</button>
|
|
<button type="button" onClick={chrome.toggleZoomMenu}>
|
|
toggle zoom
|
|
</button>
|
|
<button type="button" onClick={chrome.toggleBackgroundSettings}>
|
|
toggle background
|
|
</button>
|
|
<button type="button" onClick={chrome.toggleMinimap}>
|
|
toggle minimap
|
|
</button>
|
|
<button type="button" onClick={chrome.closeEditorChromePanels}>
|
|
close panels
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.applyCanvasBackgroundColor('#abc')}
|
|
>
|
|
apply short hex
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.handleCanvasBackgroundHexChange('#not-a-color')}
|
|
>
|
|
invalid hex
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => chrome.handleCanvasBackgroundHexChange('#ffffff')}
|
|
>
|
|
valid hex
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
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(<ChromeHarness />);
|
|
|
|
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(<ChromeHarness openEditorLoginModal={openEditorLoginModal} />);
|
|
|
|
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(<ChromeHarness />);
|
|
|
|
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');
|
|
});
|
|
});
|