636934de88
新增 editor_showcase_asset、点赞和活动配置表,生成素材默认不公开。 补齐素材提交精选审核、后台审核通过返还泥点、展示开关和公开精选分页接口。 更新素材库右键菜单和创作主页精选瀑布流,展示作者昵称、陶泥号、成本和点赞状态。 新增后台素材查询页,支持时间、用户、分类、关键词查询和缩略图详情查看。 同步 SpacetimeDB 绑定、前后端契约、项目文档和决策记录。
165 lines
4.7 KiB
TypeScript
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();
|
|
});
|
|
});
|