// @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();
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();
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();
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();
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();
await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
await user.click(screen.getByRole('button', { name: 'Add' }));
expect(createManualEntryMock).not.toHaveBeenCalled();
});
});