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>
133 lines
4.8 KiB
TypeScript
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();
|
|
});
|
|
});
|