Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion app/components/UI/Predict/hooks/usePredictOrderPreview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('usePredictOrderPreview', () => {
expect(mockPreviewOrder).toHaveBeenCalledTimes(1);
});

it('auto-refreshes preview at specified interval', async () => {
it('schedules next refresh after receiving response', async () => {
mockPreviewOrder.mockResolvedValue(mockPreview);

const params = { ...defaultParams, autoRefreshTimeout: 2000 };
Expand Down Expand Up @@ -189,6 +189,109 @@ describe('usePredictOrderPreview', () => {

expect(mockPreviewOrder).toHaveBeenCalledTimes(3);
});

it('waits for response before starting timeout countdown', async () => {
let callCount = 0;
mockPreviewOrder.mockImplementation(async () => {
callCount += 1;
return mockPreview;
});

const params = { ...defaultParams, autoRefreshTimeout: 1000 };
const { waitForNextUpdate } = renderHook(() =>
usePredictOrderPreview(params),
);

act(() => {
jest.advanceTimersByTime(100);
});

await waitForNextUpdate();

expect(callCount).toBe(1);

act(() => {
jest.advanceTimersByTime(500);
});

expect(callCount).toBe(1);

act(() => {
jest.advanceTimersByTime(500);
});

await waitForNextUpdate();

expect(callCount).toBe(2);
});

it('schedules next refresh after error response', async () => {
mockPreviewOrder.mockRejectedValue(new Error('API Error'));

const consoleErrorSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {
// Suppress console.error output during test
});

const params = { ...defaultParams, autoRefreshTimeout: 2000 };
const { waitForNextUpdate } = renderHook(() =>
usePredictOrderPreview(params),
);

act(() => {
jest.advanceTimersByTime(100);
});

await waitForNextUpdate();

expect(mockPreviewOrder).toHaveBeenCalledTimes(1);

mockPreviewOrder.mockResolvedValue(mockPreview);

act(() => {
jest.advanceTimersByTime(2000);
});

await waitForNextUpdate();

expect(mockPreviewOrder).toHaveBeenCalledTimes(2);

consoleErrorSpy.mockRestore();
});

it('clears pending refresh timer when parameters change', async () => {
mockPreviewOrder.mockResolvedValue(mockPreview);

const params = { ...defaultParams, autoRefreshTimeout: 2000 };
const { waitForNextUpdate, rerender } = renderHook(
(props: PreviewOrderParams & { autoRefreshTimeout?: number }) =>
usePredictOrderPreview(props),
{ initialProps: params },
);

act(() => {
jest.advanceTimersByTime(100);
});

await waitForNextUpdate();

expect(mockPreviewOrder).toHaveBeenCalledTimes(1);

act(() => {
jest.advanceTimersByTime(1000);
});

rerender({ ...params, size: 200 });

act(() => {
jest.advanceTimersByTime(100);
});

await waitForNextUpdate();

expect(mockPreviewOrder).toHaveBeenCalledTimes(2);
});
});

describe('error handling', () => {
Expand Down
48 changes: 36 additions & 12 deletions app/components/UI/Predict/hooks/usePredictOrderPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function usePredictOrderPreview(

const currentOperationRef = useRef<number>(0);
const isMountedRef = useRef<boolean>(true);
const refreshTimerRef = useRef<NodeJS.Timeout | null>(null);

const { previewOrder } = usePredictTrading();

Expand All @@ -35,6 +36,26 @@ export function usePredictOrderPreview(
positionId,
} = params;

// Define ref before using it in scheduleNextRefresh
const calculatePreviewRef = useRef<(() => Promise<void>) | null>(null);

const scheduleNextRefresh = useCallback(() => {
if (!autoRefreshTimeout || !isMountedRef.current) return;

// Clear any existing timer
if (refreshTimerRef.current) {
clearTimeout(refreshTimerRef.current);
}

// Schedule next refresh after the timeout
refreshTimerRef.current = setTimeout(() => {
calculatePreviewRef.current?.();
}, autoRefreshTimeout);
}, [autoRefreshTimeout]);

const scheduleNextRefreshRef = useRef(scheduleNextRefresh);
scheduleNextRefreshRef.current = scheduleNextRefresh;

const calculatePreview = useCallback(async () => {
const operationId = ++currentOperationRef.current;

Expand Down Expand Up @@ -63,6 +84,8 @@ export function usePredictOrderPreview(
if (operationId === currentOperationRef.current && isMountedRef.current) {
setPreview(p);
setError(null);
// Schedule next refresh after successful response
scheduleNextRefreshRef.current();
}
} catch (err) {
console.error('Failed to preview order:', err);
Expand Down Expand Up @@ -93,6 +116,8 @@ export function usePredictOrderPreview(
defaultCode: PREDICT_ERROR_CODES.PREVIEW_FAILED,
});
setError(parsedErrorMessage);
// Schedule next refresh after error response
scheduleNextRefreshRef.current();
}
} finally {
if (operationId === currentOperationRef.current && isMountedRef.current) {
Expand All @@ -110,32 +135,31 @@ export function usePredictOrderPreview(
positionId,
]);

const calculatePreviewRef = useRef(calculatePreview);
calculatePreviewRef.current = calculatePreview;

useEffect(
() => () => {
isMountedRef.current = false;
// Clear refresh timer on unmount
if (refreshTimerRef.current) {
clearTimeout(refreshTimerRef.current);
}
},
[],
);

useEffect(() => {
// Clear any pending refresh timer when parameters change
if (refreshTimerRef.current) {
clearTimeout(refreshTimerRef.current);
refreshTimerRef.current = null;
}

const debounceTimer = setTimeout(() => {
calculatePreviewRef.current();
calculatePreviewRef.current?.();
}, 100);

return () => clearTimeout(debounceTimer);
}, [providerId, marketId, outcomeId, outcomeTokenId, side, size]);

useEffect(() => {
if (!autoRefreshTimeout) return undefined;

const refreshTimer = setInterval(() => {
calculatePreviewRef.current();
}, autoRefreshTimeout);

return () => clearInterval(refreshTimer);
}, [
providerId,
marketId,
Expand Down
Loading