Files
Genarrative/src/components/image-editor/ImageCanvasTopbarView.test.tsx
T

248 lines
7.9 KiB
TypeScript

/* @vitest-environment jsdom */
import { fireEvent, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import type { CanvasLayer } from './ImageCanvasEditorTypes';
import { ImageCanvasTopbarView } from './ImageCanvasTopbarView';
import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts';
const walletBreakdown = {
totalPoints: 1_234,
permanentPoints: 1_000,
limitedPoints: 214,
limitedExpiresAt: '2026-07-31T16:00:00Z',
dailyFreePoints: 20,
dailyFreeResetPoints: 20,
dailyFreeResetsAt: '2026-07-12T16:00:00Z',
};
function createLayer(overrides: Partial<CanvasLayer> = {}): CanvasLayer {
const id = overrides.id ?? 'layer-a';
return {
id,
resourceId: `resource-${id}`,
title: id,
src: `data:image/png;base64,${id}`,
x: 0,
y: 0,
width: 320,
height: 180,
originalWidth: 320,
originalHeight: 180,
zIndex: 1,
sourceType: 'uploaded',
...overrides,
};
}
function renderTopbar(
overrides: Partial<Parameters<typeof ImageCanvasTopbarView>[0]> = {},
) {
useImageCanvasContextStore.getState().setProjectId('project-a');
const props: Parameters<typeof ImageCanvasTopbarView>[0] = {
projectTitle: '默认项目',
projectRenameValue: '默认项目',
isRenamingProject: false,
isProjectRenameSaving: false,
projectRenameError: null,
layers: [],
walletBalance: 1_234,
walletBreakdown,
isWalletBalanceLoading: false,
isWalletDetailsLoading: false,
walletDetailsError: null,
currentUser: null,
assetExportStatus: null,
isExportingAssets: false,
setProjectRenameValue: vi.fn(),
startProjectRename: vi.fn(),
cancelProjectRename: vi.fn(),
submitProjectRename: vi.fn(),
resetProjectRenameError: vi.fn(),
exportCanvasAssets: vi.fn(),
onOpenShortcuts: vi.fn(),
onRequestWalletDetails: vi.fn(),
onRecharge: vi.fn(),
onOpenWalletLedger: vi.fn(),
onOpenAccount: vi.fn(),
...overrides,
};
render(<ImageCanvasTopbarView {...props} />);
return props;
}
describe('ImageCanvasTopbarView', () => {
it('shows the project title and project gallery link', () => {
const props = renderTopbar();
expect(screen.getByRole('heading', { name: '默认项目' })).toBeTruthy();
expect(screen.queryByText('画布')).toBeNull();
expect(
screen.getByRole('link', { name: '返回项目页面' }).getAttribute('href'),
).toBe('/project');
fireEvent.click(screen.getByRole('button', { name: '编辑项目名称' }));
expect(props.startProjectRename).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByRole('button', { name: '查看画布快捷键' }));
expect(props.onOpenShortcuts).toHaveBeenCalledTimes(1);
});
it('shows the shared mud point breakdown and wallet actions', async () => {
const user = userEvent.setup();
const props = renderTopbar({
walletBalance: 12_345,
walletBreakdown: {
...walletBreakdown,
totalPoints: 12_345,
},
});
const walletChip = screen.getByRole('button', {
name: '泥点 12,345',
});
expect(walletChip.textContent).toBe('泥点 1.2万');
await user.hover(walletChip);
const details = screen.getByRole('dialog', { name: '泥点账户详情' });
expect(within(details).getByText('不限时泥点')).toBeTruthy();
expect(within(details).queryByText('限时泥点')).toBeNull();
expect(within(details).getByText('每日免费泥点')).toBeTruthy();
await user.click(within(details).getByRole('button', { name: '使用详情' }));
expect(props.onOpenWalletLedger).toHaveBeenCalledTimes(1);
await user.click(screen.getByRole('button', { name: '充值' }));
expect(props.onRecharge).toHaveBeenCalledTimes(1);
expect(props.onRequestWalletDetails).not.toHaveBeenCalled();
});
it('shows the current user avatar beside the mud point balance', () => {
const props = renderTopbar({
currentUser: {
id: 'user-1',
publicUserCode: 'SY-00000003',
displayName: '厉冰都',
avatarUrl: 'data:image/png;base64,AVATAR',
},
});
const accountButton = screen.getByRole('button', { name: '账号 厉冰都' });
expect(accountButton.textContent).toContain('厉冰都');
expect(accountButton.textContent).toContain('SY-00000003');
expect(accountButton.querySelector('img')?.getAttribute('src')).toBe(
'data:image/png;base64,AVATAR',
);
fireEvent.click(accountButton);
expect(props.onOpenAccount).toHaveBeenCalledTimes(1);
});
it('submits, resets, and cancels project rename edits', () => {
const props = renderTopbar({
isRenamingProject: true,
projectRenameValue: '草稿项目',
projectRenameError: '项目名称不能为空',
});
const input = screen.getByLabelText('项目名称');
fireEvent.change(input, { target: { value: '新项目' } });
fireEvent.click(screen.getByRole('button', { name: '保存项目名称' }));
expect(props.setProjectRenameValue).toHaveBeenCalledWith('新项目');
expect(props.resetProjectRenameError).toHaveBeenCalledTimes(1);
expect(props.submitProjectRename).toHaveBeenCalledWith('project-a');
expect(screen.getByRole('alert').textContent).toBe('项目名称不能为空');
fireEvent.keyDown(input, { key: 'Escape' });
expect(props.cancelProjectRename).toHaveBeenCalledTimes(1);
});
it('exports only when canvas has exportable layers and shows export status', () => {
const exportCanvasAssets = vi.fn();
const { rerender } = render(
<ImageCanvasTopbarView
projectTitle="默认项目"
projectRenameValue="默认项目"
isRenamingProject={false}
isProjectRenameSaving={false}
projectRenameError={null}
layers={[]}
walletBalance={0}
walletBreakdown={null}
isWalletBalanceLoading={false}
isWalletDetailsLoading={false}
walletDetailsError={null}
currentUser={null}
assetExportStatus={null}
isExportingAssets={false}
setProjectRenameValue={vi.fn()}
startProjectRename={vi.fn()}
cancelProjectRename={vi.fn()}
submitProjectRename={vi.fn()}
resetProjectRenameError={vi.fn()}
exportCanvasAssets={exportCanvasAssets}
onOpenShortcuts={vi.fn()}
onRequestWalletDetails={vi.fn()}
onRecharge={vi.fn()}
onOpenWalletLedger={vi.fn()}
onOpenAccount={vi.fn()}
/>,
);
expect(
(
screen.getByRole('button', {
name: '下载画布素材',
}) as HTMLButtonElement
).disabled,
).toBe(true);
rerender(
<ImageCanvasTopbarView
projectTitle="默认项目"
projectRenameValue="默认项目"
isRenamingProject={false}
isProjectRenameSaving={false}
projectRenameError={null}
layers={[createLayer()]}
walletBalance={0}
walletBreakdown={null}
isWalletBalanceLoading={false}
isWalletDetailsLoading={false}
walletDetailsError={null}
currentUser={null}
assetExportStatus={{
tone: 'success',
message: '画布素材已导出',
}}
isExportingAssets={false}
setProjectRenameValue={vi.fn()}
startProjectRename={vi.fn()}
cancelProjectRename={vi.fn()}
submitProjectRename={vi.fn()}
resetProjectRenameError={vi.fn()}
exportCanvasAssets={exportCanvasAssets}
onOpenShortcuts={vi.fn()}
onRequestWalletDetails={vi.fn()}
onRecharge={vi.fn()}
onOpenWalletLedger={vi.fn()}
onOpenAccount={vi.fn()}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '下载画布素材' }));
expect(exportCanvasAssets).toHaveBeenCalledTimes(1);
expect(screen.getByRole('status').textContent).toBe('画布素材已导出');
});
});