130 lines
5.0 KiB
TypeScript
130 lines
5.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { render, screen, waitFor, fireEvent, within } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { EntriesTab } from './EntriesTab';
|
|
|
|
const {
|
|
authState,
|
|
confirmMock,
|
|
fetchManualEntriesMock,
|
|
deleteManualEntryMock,
|
|
createManualEntryMock
|
|
} = vi.hoisted(() => ({
|
|
authState: {
|
|
user: { id: 'user-1', email: 'test@demo.test' } as any,
|
|
profile: { role: 'user' } as any
|
|
},
|
|
confirmMock: vi.fn(),
|
|
fetchManualEntriesMock: vi.fn(),
|
|
deleteManualEntryMock: vi.fn(),
|
|
createManualEntryMock: vi.fn()
|
|
}));
|
|
|
|
vi.mock('../components/AuthContext', () => ({
|
|
useAuth: () => authState
|
|
}));
|
|
|
|
vi.mock('../lib/manualEntriesApi', () => ({
|
|
fetchManualEntries: fetchManualEntriesMock,
|
|
deleteManualEntry: deleteManualEntryMock,
|
|
createManualEntry: createManualEntryMock
|
|
}));
|
|
|
|
vi.mock('../components/EntryForm', () => ({
|
|
EntryForm: ({ onSuccess, initialData }: any) => (
|
|
<div>
|
|
<span data-testid="initial-data">{initialData ? initialData.symbol : 'none'}</span>
|
|
<button onClick={onSuccess}>SubmitEntry</button>
|
|
</div>
|
|
)
|
|
}));
|
|
|
|
describe('EntriesTab master suite', () => {
|
|
const mockEntry = {
|
|
stock_instance_id: 'entry-1',
|
|
symbol: 'BTC/USDT',
|
|
active: true,
|
|
user_id: 'user-1',
|
|
buy_price: '50000',
|
|
sell_price: '55000',
|
|
quantity: '1',
|
|
status: 'active',
|
|
is_crypto: true,
|
|
is_real_trade: false,
|
|
label: 'Test Entry'
|
|
};
|
|
|
|
const mockBotState: any = {
|
|
orders: [],
|
|
symbols: {},
|
|
alerts: [],
|
|
positions: [],
|
|
history: [],
|
|
settings: { executionMode: 'Paper' }
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
authState.user = { id: 'user-1', email: 'test@demo.test' };
|
|
authState.profile = { role: 'user' };
|
|
vi.stubGlobal('confirm', confirmMock);
|
|
vi.stubGlobal('crypto', { randomUUID: () => 'uuid-123' });
|
|
fetchManualEntriesMock.mockResolvedValue([mockEntry]);
|
|
deleteManualEntryMock.mockResolvedValue(undefined);
|
|
createManualEntryMock.mockResolvedValue({});
|
|
});
|
|
|
|
it('handles entry drawer interactions', async () => {
|
|
const user = userEvent.setup();
|
|
render(<EntriesTab botState={mockBotState} />);
|
|
await waitFor(() => expect(screen.getByText('BTC/USDT')).toBeInTheDocument());
|
|
|
|
await user.click(screen.getByText(/Manual Entry Injection/i));
|
|
expect(screen.getByText(/New Opportunity Initialization/i)).toBeInTheDocument();
|
|
await user.click(screen.getByText('SubmitEntry'));
|
|
expect(screen.queryByText(/New Opportunity Initialization/i)).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('derives diverse entry states (LOCKED, BLOCKED, SUBMITTED, CONFIRMED, ORPHAN)', async () => {
|
|
const user = userEvent.setup();
|
|
const entries = [
|
|
{ stock_instance_id: 's1', symbol: 'S1', active: true, status: 'active', is_real_trade: false },
|
|
{ stock_instance_id: 's2', symbol: 'S2', active: false, status: 'cooldown', is_real_trade: false }, // Use 'cooldown' instead of 'blocked' because 'blocked' contains 'lock'
|
|
{ stock_instance_id: 's3', symbol: 'S3', active: false, status: 'submitted', is_real_trade: false },
|
|
{ stock_instance_id: 's4', symbol: 'S4', active: false, status: 'confirmed', is_real_trade: false },
|
|
{ stock_instance_id: 's5', symbol: 'S5', active: false, status: 'orphan', is_real_trade: false },
|
|
];
|
|
fetchManualEntriesMock.mockResolvedValue(entries);
|
|
|
|
render(<EntriesTab botState={mockBotState} />);
|
|
|
|
await waitFor(() => expect(screen.getByText('S1')).toBeInTheDocument());
|
|
expect(screen.getByText('LOCKED')).toBeInTheDocument();
|
|
|
|
const suspendedTab = screen.getByRole('button', { name: /Suspended/i });
|
|
await user.click(suspendedTab);
|
|
|
|
await waitFor(() => expect(screen.getByText('S2')).toBeInTheDocument(), { timeout: 3000 });
|
|
expect(screen.getByText('BLOCKED')).toBeInTheDocument();
|
|
expect(screen.getByText('SUBMITTED')).toBeInTheDocument();
|
|
expect(screen.getByText('CONFIRMED')).toBeInTheDocument();
|
|
expect(screen.getByText('ORPHAN')).toBeInTheDocument();
|
|
});
|
|
|
|
it('covers clone and delete triggers', async () => {
|
|
render(<EntriesTab botState={mockBotState} />);
|
|
await waitFor(() => expect(screen.getByText('BTC/USDT')).toBeInTheDocument());
|
|
|
|
const card = screen.getByText('BTC/USDT').closest('.group');
|
|
const buttons = within(card as HTMLElement).getAllByRole('button');
|
|
|
|
await fireEvent.click(buttons[1]);
|
|
expect(createManualEntryMock).toHaveBeenCalled();
|
|
|
|
confirmMock.mockReturnValue(true);
|
|
await fireEvent.click(buttons[2]);
|
|
expect(deleteManualEntryMock).toHaveBeenCalledWith('entry-1');
|
|
});
|
|
});
|