Files
Genarrative/apps/admin-web/src/pages/AdminEditorAssetQueryPage.test.tsx
T
kdletters 636934de88 补齐陶泥儿精选审核链路
新增 editor_showcase_asset、点赞和活动配置表,生成素材默认不公开。

补齐素材提交精选审核、后台审核通过返还泥点、展示开关和公开精选分页接口。

更新素材库右键菜单和创作主页精选瀑布流,展示作者昵称、陶泥号、成本和点赞状态。

新增后台素材查询页,支持时间、用户、分类、关键词查询和缩略图详情查看。

同步 SpacetimeDB 绑定、前后端契约、项目文档和决策记录。
2026-07-04 15:47:14 +08:00

165 lines
4.7 KiB
TypeScript

/* @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(
<AdminEditorAssetQueryPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
expect(await screen.findByText('作者昵称')).toBeTruthy();
expect(screen.getByText('SY-00000042')).toBeTruthy();
expect(screen.queryByText('user-1')).toBeNull();
});
test('后台素材查询按用户、分类和时间调用查询接口', async () => {
render(
<AdminEditorAssetQueryPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
await screen.findByRole('img', {name: '素材:角色形象 1'});
fireEvent.change(screen.getByLabelText('用户 ID'), {
target: {value: 'user-1'},
});
fireEvent.change(screen.getByLabelText('分类'), {
target: {value: 'character'},
});
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',
assetKind: 'character',
createdAfter: '2026-07-01T00:00:00+08:00',
createdBefore: '2026-07-04T23:59:59.999+08:00',
limit: 80,
});
});
});
test('后台素材查询缩略图使用 objectKey 换签后展示', async () => {
render(
<AdminEditorAssetQueryPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
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 () => {
render(
<AdminEditorAssetQueryPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
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(
<AdminEditorAssetQueryPage
token="admin-token"
onUnauthorized={vi.fn()}
/>,
);
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();
});
});