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

112 lines
5.1 KiB
TypeScript

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 点');
});
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 点'));
});
});