131 lines
3.7 KiB
TypeScript
131 lines
3.7 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act, render, screen } from '@testing-library/react';
|
|
import { useMemo, useReducer, useRef } from 'react';
|
|
import { MemoryRouter, useSearchParams } from 'react-router-dom';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { DEFAULT_TRADE_PLANS_UI_STATE, reduceTradePlansUiState } from './tradePlansState';
|
|
import { useTradePlansNavigationState } from './useTradePlansNavigationState';
|
|
|
|
function NavigationHarness() {
|
|
const [uiState, dispatch] = useReducer(reduceTradePlansUiState, DEFAULT_TRADE_PLANS_UI_STATE);
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const setupCardRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
|
|
|
const availableSellHoldings = useMemo(() => ([
|
|
{
|
|
symbol: 'AAPL',
|
|
size: 5,
|
|
entryPrice: 180,
|
|
profileId: 'p1',
|
|
tradeId: 'TRD-AAPL',
|
|
},
|
|
{
|
|
symbol: 'MSFT',
|
|
size: 2,
|
|
entryPrice: 410,
|
|
profileId: 'p1',
|
|
tradeId: 'TRD-MSFT',
|
|
},
|
|
]), []);
|
|
|
|
const savedSetups = useMemo(() => ([
|
|
{
|
|
stock_instance_id: 'setup-aapl',
|
|
symbol: 'AAPL',
|
|
workflow_type: 'simple',
|
|
simple_side: 'buy',
|
|
status: 'simple_bought',
|
|
active: true,
|
|
is_crypto: false,
|
|
is_real_trade: false,
|
|
},
|
|
{
|
|
stock_instance_id: 'setup-msft',
|
|
symbol: 'MSFT',
|
|
workflow_type: 'simple',
|
|
simple_side: 'buy',
|
|
status: 'simple_bought',
|
|
active: true,
|
|
is_crypto: false,
|
|
is_real_trade: false,
|
|
},
|
|
] as any[]), []);
|
|
|
|
const applyHoldingToDraft = (holding: { symbol: string; size: number; entryPrice: number; profileId?: string; tradeId?: string }) => {
|
|
dispatch({
|
|
type: 'apply-holding',
|
|
tradeId: holding.tradeId || null,
|
|
symbol: holding.symbol,
|
|
quantity: String(holding.size),
|
|
});
|
|
};
|
|
|
|
useTradePlansNavigationState({
|
|
searchParams,
|
|
setSearchParams,
|
|
savedSetups,
|
|
availableSellHoldings,
|
|
applyHoldingToDraft,
|
|
dispatch,
|
|
setupCardRefs,
|
|
});
|
|
|
|
return (
|
|
<div>
|
|
<button type="button" onClick={() => setSearchParams({ setupId: 'setup-msft' })}>
|
|
focus msft
|
|
</button>
|
|
<div data-testid="symbol">{uiState.draft.symbol}</div>
|
|
<div data-testid="holding">{uiState.selectedHoldingTradeId || ''}</div>
|
|
<div data-testid="focused">{uiState.focusedSetupId || ''}</div>
|
|
<div data-testid="message">{uiState.message || ''}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
describe('useTradePlansNavigationState', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
Object.defineProperty(window, 'requestAnimationFrame', {
|
|
writable: true,
|
|
value: (cb: FrameRequestCallback) => {
|
|
cb(0);
|
|
return 0;
|
|
},
|
|
});
|
|
Object.defineProperty(Element.prototype, 'scrollIntoView', {
|
|
writable: true,
|
|
value: vi.fn(),
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('applies sell prefill and responds to repeated same-page setup focus changes', () => {
|
|
render(
|
|
<MemoryRouter initialEntries={['/plans?mode=sell&symbol=AAPL&tradeId=TRD-AAPL']}>
|
|
<NavigationHarness />
|
|
</MemoryRouter>,
|
|
);
|
|
|
|
expect(screen.getByTestId('symbol').textContent).toBe('AAPL');
|
|
expect(screen.getByTestId('holding').textContent).toBe('TRD-AAPL');
|
|
expect(screen.getByTestId('message').textContent).toContain('Loaded AAPL from Portfolio');
|
|
|
|
act(() => {
|
|
screen.getByRole('button', { name: 'focus msft' }).click();
|
|
});
|
|
|
|
expect(screen.getByTestId('focused').textContent).toBe('setup-msft');
|
|
expect(screen.getByTestId('message').textContent).toContain('Focused saved plan for MSFT');
|
|
|
|
act(() => {
|
|
vi.advanceTimersByTime(2200);
|
|
});
|
|
|
|
expect(screen.getByTestId('focused').textContent).toBe('');
|
|
});
|
|
});
|