Files
makelore/tests/unit/sidebar-token-points.test.tsx
T

113 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Sidebar } from '@/components/layout/Sidebar';
import { billingSummary, permanentBalance } from '../fixtures/permanent-points';
const fetchBillingSummaryMock = vi.hoisted(() => vi.fn());
const getValidAccessTokenMock = vi.hoisted(() => vi.fn());
const authState = vi.hoisted(() => ({
user: {
username: 'member@example.com',
userId: 'user-1',
tenantId: null,
deptId: null,
authorities: [],
},
getValidAccessToken: getValidAccessTokenMock,
logout: vi.fn(),
}));
const codingState = vi.hoisted(() => ({
projects: [],
activeProject: null,
load: vi.fn().mockResolvedValue(undefined),
setActiveProject: vi.fn(),
removeProject: vi.fn(),
createProject: vi.fn(),
}));
const projectConfigState = vi.hoisted(() => ({
configsByProjectId: {},
load: vi.fn().mockResolvedValue(undefined),
remove: vi.fn(),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (_key: string, fallback: string) => fallback }),
}));
vi.mock('@/lib/works-billing', () => ({
fetchWorksBillingSummary: (...args: unknown[]) => fetchBillingSummaryMock(...args),
fetchWorksRechargeOrders: vi.fn().mockResolvedValue({ items: [] }),
fetchWorksPointHistory: vi.fn().mockResolvedValue({ items: [], has_more: false }),
createWorksRechargeOrder: vi.fn(), fetchWorksRechargeOrder: vi.fn(), openWorksBillingAccount: vi.fn(),
}));
vi.mock('@/lib/api-client', () => ({ invokeIpc: vi.fn() }));
vi.mock('@/hooks/use-current-user-profile', () => ({
useCurrentUserProfile: () => ({
userProfile: { displayName: '测试用户', avatarUrl: null },
profileRequired: false,
profileSyncError: null,
syncProfileNow: vi.fn().mockResolvedValue(undefined),
}),
}));
vi.mock('@/stores/auth', () => ({
useAuthStore: (selector: (state: typeof authState) => unknown) => selector(authState),
}));
vi.mock('@/stores/settings', () => ({
useSettingsStore: (selector: (state: { sidebarCollapsed: boolean }) => unknown) => (
selector({ sidebarCollapsed: false })
),
}));
vi.mock('@/stores/coding-workspace', () => ({
useCodingWorkspaceStore: (selector: (state: typeof codingState) => unknown) => selector(codingState),
}));
vi.mock('@/stores/project-config', () => ({
useProjectConfigStore: (selector: (state: typeof projectConfigState) => unknown) => (
selector(projectConfigState)
),
}));
vi.mock('@/stores/providers', () => ({
useProviderStore: (selector: (state: { refreshProviderSnapshot: () => Promise<void> }) => unknown) => (
selector({ refreshProviderSnapshot: vi.fn().mockResolvedValue(undefined) })
),
}));
vi.mock('@/components/layout/ModuleSwitcher', () => ({ ModuleSwitcher: () => null }));
vi.mock('@/components/layout/ImageWorkspaceSidebar', () => ({ ImageWorkspaceSidebar: () => null }));
vi.mock('@/components/layout/SidebarUpdateButton', () => ({ SidebarUpdateButton: () => null }));
vi.mock('@/components/profile/UserProfileDialog', () => ({ UserProfileDialog: () => null }));
async function openAccount() {
render(<MemoryRouter initialEntries={['/coding']}><Sidebar /></MemoryRouter>);
fireEvent.click(screen.getByTestId('sidebar-member-menu-trigger'));
}
describe('Sidebar permanent point wallet', () => {
beforeEach(() => {
fetchBillingSummaryMock.mockReset();
fetchBillingSummaryMock.mockResolvedValue(billingSummary());
});
it('opens the point wallet from the account menu without retired member controls', async () => {
await openAccount();
await waitFor(() => expect(screen.getByTestId('sidebar-total-token-points')).toHaveTextContent('75 点'));
expect(screen.queryByText(/重置卡|升级订阅|会员等级|本周剩余/)).not.toBeInTheDocument();
fireEvent.click(screen.getByTestId('sidebar-point-wallet-open'));
expect(screen.getByTestId('point-wallet-dialog')).toHaveTextContent('1 元充值 50 点');
expect(screen.getByTestId('point-recharge-recipient')).toHaveTextContent('测试用户(member@example.com)');
});
it('shows a retryable failure without inventing a zero balance', async () => {
fetchBillingSummaryMock.mockRejectedValue(new Error('offline'));
await openAccount();
await waitFor(() => expect(screen.getByTestId('sidebar-total-token-points')).toHaveTextContent('暂时不可用'));
fetchBillingSummaryMock.mockResolvedValue(billingSummary());
fireEvent.click(screen.getByRole('button', { name: '重试余额' }));
await waitFor(() => expect(screen.getByTestId('sidebar-total-token-points')).toHaveTextContent('75 点'));
});
it('refreshes the balance when reopening the account menu after consumption', async () => {
await openAccount();
await waitFor(() => expect(screen.getByTestId('sidebar-total-token-points')).toHaveTextContent('75 点'));
fireEvent.click(screen.getByTestId('sidebar-member-menu-trigger'));
fetchBillingSummaryMock.mockResolvedValue(billingSummary({ own_points: permanentBalance({ total_remaining: '50.00' }) }));
fireEvent.click(screen.getByTestId('sidebar-member-menu-trigger'));
await waitFor(() => expect(screen.getByTestId('sidebar-total-token-points')).toHaveTextContent('50 点'));
});
});