v0.0.01b
This commit is contained in:
150
src/pages/AccountPage.test.tsx
Normal file
150
src/pages/AccountPage.test.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
||||
import { AccountPage } from './AccountPage';
|
||||
import { api } from '../api/client';
|
||||
import { useAuthStore } from '../store/useStore';
|
||||
|
||||
vi.mock('../api/client', () => ({
|
||||
api: {
|
||||
getPlans: vi.fn(),
|
||||
checkout: vi.fn(),
|
||||
getMe: vi.fn(),
|
||||
getSubscription: vi.fn(),
|
||||
getDevices: vi.fn(),
|
||||
deleteDevice: vi.fn(),
|
||||
login: vi.fn(),
|
||||
signup: vi.fn(),
|
||||
},
|
||||
ApiError: class ApiError extends Error {
|
||||
constructor(m: string, public status: number) {
|
||||
super(m);
|
||||
}
|
||||
},
|
||||
setStoredTokens: vi.fn(),
|
||||
clearStoredTokens: vi.fn(),
|
||||
}));
|
||||
|
||||
function renderAccount(authed = true) {
|
||||
if (authed) {
|
||||
useAuthStore.setState({
|
||||
access: 'a',
|
||||
refresh: 'r',
|
||||
expiresAt: 9999999999,
|
||||
user: { username: 'alice', telegram_id: 'alice_id', referal_code: 'c' },
|
||||
remember: true,
|
||||
});
|
||||
} else {
|
||||
useAuthStore.setState({
|
||||
access: null,
|
||||
refresh: null,
|
||||
expiresAt: null,
|
||||
user: null,
|
||||
remember: true,
|
||||
});
|
||||
}
|
||||
return render(
|
||||
<MemoryRouter initialEntries={['/account']}>
|
||||
<Routes>
|
||||
<Route path="/account" element={<AccountPage />} />
|
||||
<Route path="/login" element={<div>login page</div>} />
|
||||
<Route path="/" element={<div>landing</div>} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
}
|
||||
|
||||
const ME = { username: 'alice', telegram_id: 'alice_id', referal_code: 'c' };
|
||||
const SUB = {
|
||||
devices: 6,
|
||||
expires_at: '2026-03-14T00:00:00Z',
|
||||
addon_ids: [],
|
||||
subscription_link: 'link',
|
||||
has_subscription: true,
|
||||
};
|
||||
const DEVICES = [
|
||||
{ os: 'macos', model: 'MacBook Pro', client: 'Malenia', hwid: 'h1' },
|
||||
{ os: 'ios', model: 'iPhone 15', client: 'Malenia', hwid: 'h2' },
|
||||
];
|
||||
|
||||
describe('AccountPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(api.getMe).mockReset();
|
||||
vi.mocked(api.getSubscription).mockReset();
|
||||
vi.mocked(api.getDevices).mockReset();
|
||||
vi.mocked(api.deleteDevice).mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it('redirects to /login when not authenticated', async () => {
|
||||
renderAccount(false);
|
||||
await waitFor(() => expect(screen.getByText('login page')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('renders hero username and stat strip', async () => {
|
||||
vi.mocked(api.getMe).mockResolvedValue(ME);
|
||||
vi.mocked(api.getSubscription).mockResolvedValue(SUB);
|
||||
vi.mocked(api.getDevices).mockResolvedValue(DEVICES);
|
||||
renderAccount();
|
||||
expect(await screen.findByText('alice')).toBeInTheDocument();
|
||||
expect(screen.getByText('@alice_id')).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/iPhone 15/)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not render a subscription status before subscription data loads', async () => {
|
||||
vi.mocked(api.getMe).mockResolvedValue(ME);
|
||||
vi.mocked(api.getSubscription).mockResolvedValue(SUB);
|
||||
vi.mocked(api.getDevices).mockResolvedValue(DEVICES);
|
||||
renderAccount();
|
||||
|
||||
expect(screen.queryByText(/активная подписка отсутствует/i)).not.toBeInTheDocument();
|
||||
expect(await screen.findByText(/подписка до/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the subscription link and copy action', async () => {
|
||||
vi.mocked(api.getMe).mockResolvedValue(ME);
|
||||
vi.mocked(api.getSubscription).mockResolvedValue(SUB);
|
||||
vi.mocked(api.getDevices).mockResolvedValue(DEVICES);
|
||||
renderAccount();
|
||||
expect(await screen.findByText(SUB.subscription_link)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /копировать/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('renders empty state when no devices', async () => {
|
||||
vi.mocked(api.getMe).mockResolvedValue(ME);
|
||||
vi.mocked(api.getSubscription).mockResolvedValue(SUB);
|
||||
vi.mocked(api.getDevices).mockResolvedValue([]);
|
||||
renderAccount();
|
||||
expect(await screen.findByText(/нет подключённых устройств/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens confirmation modal and deletes device on confirm', async () => {
|
||||
vi.mocked(api.getMe).mockResolvedValue(ME);
|
||||
vi.mocked(api.getSubscription).mockResolvedValue(SUB);
|
||||
vi.mocked(api.getDevices).mockResolvedValue(DEVICES);
|
||||
vi.mocked(api.deleteDevice).mockResolvedValue({ success: true });
|
||||
const user = userEvent.setup();
|
||||
renderAccount();
|
||||
await screen.findByText(/iPhone 15/);
|
||||
await user.click(screen.getByRole('button', { name: /удалить iPhone 15/i }));
|
||||
expect(await screen.findByText(/удалить это устройство\?/i)).toBeInTheDocument();
|
||||
const confirmBtns = screen.getAllByRole('button', { name: /^удалить$/i });
|
||||
await user.click(confirmBtns[confirmBtns.length - 1]!);
|
||||
await waitFor(() => expect(api.deleteDevice).toHaveBeenCalledWith('h2'));
|
||||
await waitFor(() => expect(screen.queryByText(/iPhone 15/)).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('signs out and navigates to landing', async () => {
|
||||
vi.mocked(api.getMe).mockResolvedValue(ME);
|
||||
vi.mocked(api.getSubscription).mockResolvedValue(SUB);
|
||||
vi.mocked(api.getDevices).mockResolvedValue(DEVICES);
|
||||
const user = userEvent.setup();
|
||||
renderAccount();
|
||||
await screen.findByText(/alice/);
|
||||
await user.click(screen.getByRole('button', { name: /выйти/i }));
|
||||
await waitFor(() => expect(screen.getByText('landing')).toBeInTheDocument());
|
||||
expect(useAuthStore.getState().access).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user