/* @vitest-environment jsdom */ import {fireEvent, render, screen, waitFor, within} from '@testing-library/react'; import {beforeEach, expect, test, vi} from 'vitest'; import { getAdminAssetReadUrl, listAdminEditorAssets, } from '../api/adminApiClient'; import type {AdminEditorAssetPayload} from '../api/adminApiTypes'; import {AdminEditorAssetQueryPage} from './AdminEditorAssetQueryPage'; vi.mock('../api/adminApiClient', () => ({ getAdminAssetReadUrl: vi.fn(), listAdminEditorAssets: vi.fn(), })); const generatedAsset: AdminEditorAssetPayload = { assetId: 'asset-1', ownerUserId: 'user-1', authorDisplayName: '作者昵称', authorPublicUserCode: 'SY-00000042', folderId: 'folder-1', label: '角色形象 1', assetObjectId: 'asset-object-1', imageSrc: '/generated-character-drafts/editor/spec.png', objectKey: 'generated-character-drafts/editor/spec.png', width: 1024, height: 1024, sourceType: 'generated', prompt: '完整提示词内容', actualPrompt: null, model: 'gpt-image-2', provider: 'character', taskId: 'task-1', assetKind: 'character', generationInputs: {style: 'clay'}, sourceResourceId: 'resource-1', thumbnailSrc: null, generationCostMudPoints: 12, createdAt: '2026-07-04T10:00:00Z', updatedAt: '2026-07-04T10:00:00Z', }; beforeEach(() => { vi.clearAllMocks(); vi.mocked(listAdminEditorAssets).mockResolvedValue({ entries: [generatedAsset], nextCursor: null, }); vi.mocked(getAdminAssetReadUrl).mockResolvedValue({ read: { objectKey: 'generated-character-drafts/editor/spec.png', signedUrl: 'https://signed.example.com/generated-character-drafts/editor/spec.png', expiresAt: '2026-07-04T11:00:00Z', }, }); }); test('后台素材查询展示作者昵称和陶泥号', async () => { render( , ); expect(await screen.findByText('作者昵称')).toBeTruthy(); expect(screen.getByText('SY-00000042')).toBeTruthy(); expect(screen.queryByText('user-1')).toBeNull(); }); test('后台素材查询按用户、搜索和时间调用查询接口', async () => { render( , ); await screen.findByRole('img', {name: '素材:角色形象 1'}); fireEvent.change(screen.getByLabelText('用户 ID'), { target: {value: 'user-1'}, }); fireEvent.change(screen.getByLabelText('搜索'), { target: {value: '陶泥角色'}, }); fireEvent.change(screen.getByLabelText('开始时间'), { target: {value: '2026-07-01'}, }); fireEvent.change(screen.getByLabelText('结束时间'), { target: {value: '2026-07-04'}, }); await waitFor(() => { expect(listAdminEditorAssets).toHaveBeenLastCalledWith('admin-token', { ownerUserId: 'user-1', keyword: '陶泥角色', createdAfter: '2026-07-01T00:00:00+08:00', createdBefore: '2026-07-04T23:59:59.999+08:00', limit: 80, }); }); }); test('后台素材查询不展示分类筛选和分类列', async () => { render( , ); expect(await screen.findByText('角色形象 1')).toBeTruthy(); expect(screen.queryByLabelText('分类')).toBeNull(); expect(screen.queryByRole('columnheader', {name: '分类'})).toBeNull(); }); test('后台素材查询缩略图使用 objectKey 换签后展示', async () => { render( , ); const image = await screen.findByRole('img', {name: '素材:角色形象 1'}); await waitFor(() => { expect(image.getAttribute('src')).toBe( 'https://signed.example.com/generated-character-drafts/editor/spec.png', ); }); expect(getAdminAssetReadUrl).toHaveBeenCalledWith({ objectKey: 'generated-character-drafts/editor/spec.png', expireSeconds: 300, }); }); test('后台素材查询格式化微秒时间文本', async () => { vi.mocked(listAdminEditorAssets).mockResolvedValueOnce({ entries: [ { ...generatedAsset, createdAt: '1783231493.573727Z', updatedAt: '1783231493.573727Z', }, ], nextCursor: null, }); render( , ); expect(await screen.findByText('角色形象 1')).toBeTruthy(); expect(screen.queryByText('1783231493.573727Z')).toBeNull(); }); test('后台素材查询可查看素材详情', async () => { render( , ); fireEvent.click(await screen.findByRole('button', {name: '详情'})); const dialog = screen.getByRole('dialog', {name: '素材详情'}); expect(within(dialog).getByText('asset-1')).toBeTruthy(); expect(within(dialog).getByText('1024 x 1024')).toBeTruthy(); expect(within(dialog).getByText('12 泥点')).toBeTruthy(); expect(within(dialog).getByText(/"style": "clay"/u)).toBeTruthy(); }); test('后台素材查询可打开弹窗查看完整提示词', async () => { render( , ); fireEvent.click(await screen.findByRole('button', {name: '完整提示词内容'})); const dialog = screen.getByRole('dialog', {name: '完整提示词'}); expect(within(dialog).getByText('完整提示词内容')).toBeTruthy(); fireEvent.click(within(dialog).getByRole('button', {name: '关闭完整提示词'})); await waitFor(() => { expect(screen.queryByRole('dialog', {name: '完整提示词'})).toBeNull(); }); });