补充图片素材选择器交互测试
覆盖选择、清除、预览占位、失效引用、只读和键盘操作。
This commit is contained in:
+3
-3
@@ -3,7 +3,6 @@ import { useState } from 'react';
|
||||
|
||||
import { ThemedModal } from '../../../../../components/modal/ThemedModal';
|
||||
import type { SpriteAsset } from '../../../../../features/ui-editor/types/SpriteAsset';
|
||||
import { ComponentField } from './ComponentField';
|
||||
|
||||
export function ImageAssetSelector({
|
||||
value,
|
||||
@@ -50,7 +49,8 @@ export function ImageAssetSelector({
|
||||
|
||||
return (
|
||||
<>
|
||||
<ComponentField label="目标素材">
|
||||
<div className="block text-[11px] font-semibold text-(--platform-text-soft)">
|
||||
目标素材
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<div
|
||||
className={`min-w-0 flex-1 truncate rounded-lg border border-(--platform-subpanel-border) bg-white/65 px-2 py-2 text-xs ${value && !selectedSprite ? 'text-red-600' : 'text-(--platform-text-strong)'}`}
|
||||
@@ -67,7 +67,7 @@ export function ImageAssetSelector({
|
||||
{buttonLabel}
|
||||
</button>
|
||||
</div>
|
||||
</ComponentField>
|
||||
</div>
|
||||
|
||||
<ThemedModal
|
||||
open={open}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/** @vitest-environment jsdom */
|
||||
import { cleanup, render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ImageAssetSelector } from '../src/view/ui-editor/components/Inspector/Components/ImageAssetSelector';
|
||||
|
||||
const sprites = {
|
||||
hero: {
|
||||
asset_id: 'hero',
|
||||
metadata: { name: '主角', asset_type: 'sprite' },
|
||||
path: 'assets/hero.png',
|
||||
pixel_size: [32, 32] as [number, number],
|
||||
pixels_per_unit: 1,
|
||||
border: { left: 0, right: 0, top: 0, bottom: 0 },
|
||||
},
|
||||
enemy: {
|
||||
asset_id: 'enemy',
|
||||
metadata: { name: '敌人', asset_type: 'sprite' },
|
||||
path: 'assets/enemy.png',
|
||||
pixel_size: [32, 32] as [number, number],
|
||||
pixels_per_unit: 1,
|
||||
border: { left: 0, right: 0, top: 0, bottom: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
function renderSelector(
|
||||
value: string | null = null,
|
||||
onChange = vi.fn(),
|
||||
previewUrls: Record<string, string> = {},
|
||||
) {
|
||||
return render(
|
||||
<ImageAssetSelector
|
||||
value={value}
|
||||
sprites={sprites}
|
||||
previewUrls={previewUrls}
|
||||
readOnly={false}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('ImageAssetSelector', () => {
|
||||
afterEach(() => cleanup());
|
||||
|
||||
it('selects a sprite and clears the selection from the modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
renderSelector(null, onChange);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '选择素材' }));
|
||||
expect(screen.getByRole('dialog', { name: '选择图片素材' })).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: /主角/ }));
|
||||
expect(onChange).toHaveBeenCalledWith('hero');
|
||||
expect(screen.queryByRole('dialog')).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '选择素材' }));
|
||||
await user.click(screen.getByRole('button', { name: /清除选择/ }));
|
||||
expect(onChange).toHaveBeenLastCalledWith(null);
|
||||
});
|
||||
|
||||
it('shows preview images, placeholders, and invalid references', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderSelector('missing', vi.fn(), { hero: 'data:image/png;base64,abc' });
|
||||
|
||||
expect(screen.getByText('素材不存在(missing)')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: '更换素材' }));
|
||||
const dialog = screen.getByRole('dialog', { name: '选择图片素材' });
|
||||
expect(within(dialog).getByAltText('主角')).toBeTruthy();
|
||||
expect(within(dialog).getByText('素材不存在')).toBeTruthy();
|
||||
expect(
|
||||
within(dialog).getByRole('button', { name: /清除选择/ }),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('cannot open while read only', () => {
|
||||
render(
|
||||
<ImageAssetSelector
|
||||
value="hero"
|
||||
sprites={sprites}
|
||||
previewUrls={{}}
|
||||
readOnly
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', { name: '更换素材' })).toHaveProperty(
|
||||
'disabled',
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('supports keyboard activation for sprite cards', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
renderSelector(null, onChange);
|
||||
await user.click(screen.getByRole('button', { name: '选择素材' }));
|
||||
const hero = screen.getByRole('button', { name: /主角/ });
|
||||
hero.focus();
|
||||
await user.keyboard('{Enter}');
|
||||
expect(onChange).toHaveBeenCalledWith('hero');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user