Files
Genarrative/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx
T
kdletters ec34f85006 收紧 React 测试契约
将画布相关 hook 测试改为直接验证公开返回值

移除组件测试中对 class、图标和测试专用 DOM 状态的脆弱断言

新增 React 组件测试准则并同步项目记忆
2026-06-26 19:37:28 +08:00

157 lines
4.9 KiB
TypeScript

/* @vitest-environment jsdom */
import { createRef, type RefObject } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ImageCanvasBottomToolbarView } from './ImageCanvasBottomToolbarView';
describe('ImageCanvasBottomToolbarView', () => {
function renderToolbar(
overrides: Partial<Parameters<typeof ImageCanvasBottomToolbarView>[0]> = {},
) {
const switchTool = vi.fn();
const specToolWrapRef = createRef<HTMLSpanElement>();
const musicToolWrapRef = createRef<HTMLSpanElement>();
const publicationToolWrapRef = createRef<HTMLSpanElement>();
const view = render(
<ImageCanvasBottomToolbarView
specToolWrapRef={specToolWrapRef}
musicToolWrapRef={musicToolWrapRef}
publicationToolWrapRef={publicationToolWrapRef}
effectiveTool="generate"
onSwitchTool={switchTool}
onOpenToolOptions={vi.fn()}
onCloseToolOptions={vi.fn()}
{...overrides}
/>,
);
return {
...view,
toolbar: screen.getByRole('toolbar', { name: 'AI画布工具栏' }),
switchTool,
refs: {
specToolWrapRef,
musicToolWrapRef,
publicationToolWrapRef,
},
};
}
function renderToolbarView(
effectiveTool: Parameters<
typeof ImageCanvasBottomToolbarView
>[0]['effectiveTool'],
refs: {
specToolWrapRef: RefObject<HTMLSpanElement | null>;
musicToolWrapRef: RefObject<HTMLSpanElement | null>;
publicationToolWrapRef: RefObject<HTMLSpanElement | null>;
},
) {
return (
<ImageCanvasBottomToolbarView
specToolWrapRef={refs.specToolWrapRef}
musicToolWrapRef={refs.musicToolWrapRef}
publicationToolWrapRef={refs.publicationToolWrapRef}
effectiveTool={effectiveTool}
onSwitchTool={vi.fn()}
onOpenToolOptions={vi.fn()}
onCloseToolOptions={vi.fn()}
/>
);
}
it('renders the expected canvas tools and forwards user selections', async () => {
const user = userEvent.setup();
const { toolbar, switchTool } = renderToolbar();
const toolExpectations = [
['选择工具', 'select'],
['抓手工具', 'hand'],
['上传工具', 'upload'],
['生成图片', 'generate'],
['生成视频', 'video'],
['生成音乐', 'music'],
['生成规范', 'spec'],
['生成角色形象', 'character'],
['生成图标素材', 'icon'],
['生成UI设计图', 'ui-design'],
['宣发素材', 'publication'],
] as const;
for (const [label, tool] of toolExpectations) {
await user.click(within(toolbar).getByRole('button', { name: label }));
expect(switchTool).toHaveBeenLastCalledWith(tool);
}
expect(switchTool).toHaveBeenCalledTimes(toolExpectations.length);
});
it('only exposes persistent pressed state for navigation tools', () => {
const { toolbar, rerender, refs } = renderToolbar({
effectiveTool: 'select',
});
expect(
within(toolbar)
.getByRole('button', { name: '选择工具' })
.getAttribute('aria-pressed'),
).toBe('true');
expect(
within(toolbar)
.getByRole('button', { name: '生成图片' })
.getAttribute('aria-pressed'),
).toBe('false');
rerender(renderToolbarView('hand', refs));
expect(
within(toolbar)
.getByRole('button', { name: '抓手工具' })
.getAttribute('aria-pressed'),
).toBe('true');
rerender(renderToolbarView('music', refs));
expect(
within(toolbar)
.getByRole('button', { name: '生成音乐' })
.getAttribute('aria-pressed'),
).toBe('false');
});
it('opens and closes bottom option tools on pointer and keyboard focus', async () => {
const user = userEvent.setup();
const openToolOptions = vi.fn();
const closeToolOptions = vi.fn();
const { toolbar, refs } = renderToolbar({
onOpenToolOptions: openToolOptions,
onCloseToolOptions: closeToolOptions,
});
await user.hover(within(toolbar).getByRole('button', { name: '生成规范' }));
await user.unhover(
within(toolbar).getByRole('button', { name: '生成规范' }),
);
await user.hover(within(toolbar).getByRole('button', { name: '生成音乐' }));
await user.unhover(
within(toolbar).getByRole('button', { name: '生成音乐' }),
);
const publicationTool = within(toolbar).getByRole('button', {
name: '宣发素材',
});
publicationTool.focus();
publicationTool.blur();
expect(openToolOptions).toHaveBeenCalledWith('spec');
expect(closeToolOptions).toHaveBeenCalledWith('spec');
expect(openToolOptions).toHaveBeenCalledWith('music');
expect(closeToolOptions).toHaveBeenCalledWith('music');
expect(openToolOptions).toHaveBeenCalledWith('publication');
expect(closeToolOptions).toHaveBeenCalledWith('publication');
});
});