learning_ai_invt_trdg/web/src/components/EntryForm.dom.test.tsx
Saravana Achu Mac ece7fa9504 test(F7,F8): fix tab flag cache and entry alert specs
Reset the tab feature flag cache between DOM tests so one authenticated response cannot leak into fallback cases, and make the trade execution failure spec submit a valid executable order before asserting the backend error alert. This closes the documented pre-existing web test failures and restores the full Vitest suite to green.

Refs: docs/AUDIT_REDESIGN.md items F7 and F8.

Co-Authored-By: GPT-5 Codex <noreply@openai.com>
2026-05-04 15:10:59 -07:00

133 lines
4.8 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { EntryForm } from './EntryForm';
const {
getPlatformAccessTokenMock,
createManualEntryMock,
updateManualEntryMock,
authMock
} = vi.hoisted(() => ({
getPlatformAccessTokenMock: vi.fn(),
createManualEntryMock: vi.fn(),
updateManualEntryMock: vi.fn(),
authMock: { user: { id: 'user-1' } as any }
}));
vi.mock('../components/AuthContext', () => ({
useAuth: () => authMock
}));
vi.mock('../lib/authSession', () => ({
getPlatformAccessToken: getPlatformAccessTokenMock
}));
vi.mock('../lib/manualEntriesApi', () => ({
createManualEntry: createManualEntryMock,
updateManualEntry: updateManualEntryMock
}));
describe('EntryForm DOM flow', () => {
const alertMock = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
createManualEntryMock.mockReset();
updateManualEntryMock.mockReset();
getPlatformAccessTokenMock.mockReset();
authMock.user = { id: 'user-1' };
createManualEntryMock.mockResolvedValue({});
updateManualEntryMock.mockResolvedValue({});
getPlatformAccessTokenMock.mockRejectedValue(new Error('Not authenticated'));
vi.stubGlobal('fetch', vi.fn());
vi.stubGlobal('confirm', vi.fn(() => true));
vi.stubGlobal('alert', alertMock);
vi.spyOn(crypto, 'randomUUID').mockReturnValue('11111111-1111-1111-1111-111111111111');
});
it('inserts a new manual entry with numeric normalization', async () => {
const onSuccess = vi.fn();
const user = userEvent.setup();
render(<EntryForm onSuccess={onSuccess} />);
await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
await user.type(screen.getByPlaceholderText('0.00'), '100');
await user.type(screen.getByPlaceholderText('0'), '1');
await user.click(screen.getByRole('button', { name: 'Add' }));
await waitFor(() => {
expect(createManualEntryMock).toHaveBeenCalled();
expect(onSuccess).toHaveBeenCalled();
});
});
it('alerts error when trade execution fails', async () => {
const onSuccess = vi.fn();
const user = userEvent.setup();
getPlatformAccessTokenMock.mockResolvedValue('valid-token');
vi.mocked(fetch).mockResolvedValue({
ok: false,
json: async () => ({ success: false, error: 'Insufficient funds' })
} as any);
render(<EntryForm onSuccess={onSuccess} />);
await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
await user.type(screen.getByPlaceholderText('0'), '1');
await user.click(screen.getByRole('checkbox', { name: /Execute/i }));
await user.click(screen.getByRole('button', { name: 'Add' }));
await waitFor(() => {
expect(alertMock).toHaveBeenCalledWith(expect.stringContaining('Execution Failed: Insufficient funds'));
});
});
it('journals a past trade (entry + exit both provided)', async () => {
const onSuccess = vi.fn();
const user = userEvent.setup();
render(<EntryForm onSuccess={onSuccess} />);
await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
await user.type(screen.getByPlaceholderText('0.00'), '100');
await user.type(screen.getByPlaceholderText('Exit'), '110');
await user.type(screen.getByPlaceholderText('0'), '1');
await user.click(screen.getByRole('button', { name: 'Add' }));
await waitFor(() => {
expect(createManualEntryMock).toHaveBeenCalledTimes(1);
});
expect(createManualEntryMock.mock.calls[0][0]).toEqual(expect.objectContaining({
symbol: 'BTC/USD',
buy_price: 100,
sell_price: 110
}));
});
it('handles database error by alerting user', async () => {
createManualEntryMock.mockRejectedValueOnce(new Error('DB Error'));
const user = userEvent.setup();
render(<EntryForm onSuccess={vi.fn()} />);
await user.type(screen.getByPlaceholderText('BTC/USD'), 'FAIL/USD');
await user.click(screen.getByRole('button', { name: 'Add' }));
await waitFor(() => {
expect(alertMock).toHaveBeenCalledWith(expect.stringContaining('DB Error'));
});
});
it('does nothing when submitted without a user', async () => {
authMock.user = null;
const user = userEvent.setup();
render(<EntryForm onSuccess={vi.fn()} />);
await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
await user.click(screen.getByRole('button', { name: 'Add' }));
expect(createManualEntryMock).not.toHaveBeenCalled();
});
});