diff --git a/packages/ui-components/src/__tests__/OrderDetail.test.ts b/packages/ui-components/src/__tests__/OrderDetail.test.ts index ff7a1b45f9..d67ab98296 100644 --- a/packages/ui-components/src/__tests__/OrderDetail.test.ts +++ b/packages/ui-components/src/__tests__/OrderDetail.test.ts @@ -248,6 +248,94 @@ describe("OrderDetail", () => { }); }); + it("renders a remove dropdown (not a bare remove button) when onRemoveAndWithdrawAll is provided", async () => { + mockMatchesAccount.mockReturnValue(true); + render(OrderDetail, { + props: { ...defaultProps, onRemoveAndWithdrawAll: vi.fn() }, + context: new Map([["$$_queryClient", queryClient]]), + }); + + await waitFor(() => { + // When onRemoveAndWithdrawAll is provided, the dropdown trigger renders. + expect(screen.getByTestId("remove-order-menu")).toBeInTheDocument(); + }); + + // Dropdown items are hidden until the trigger is opened. + expect( + screen.queryByTestId("remove-and-withdraw-all-button"), + ).not.toBeInTheDocument(); + + await userEvent.click(screen.getByTestId("remove-order-menu")); + + await waitFor(() => { + expect(screen.getByTestId("remove-button")).toBeInTheDocument(); + expect( + screen.getByTestId("remove-and-withdraw-all-button"), + ).toBeInTheDocument(); + }); + }); + + it("calls onRemove (not onRemoveAndWithdrawAll) when the Remove dropdown item is clicked", async () => { + mockMatchesAccount.mockReturnValue(true); + const onRemove = vi.fn(); + const onRemoveAndWithdrawAll = vi.fn(); + render(OrderDetail, { + props: { ...defaultProps, onRemove, onRemoveAndWithdrawAll }, + context: new Map([["$$_queryClient", queryClient]]), + }); + + await waitFor(() => { + expect(screen.getByTestId("remove-order-menu")).toBeInTheDocument(); + }); + await userEvent.click(screen.getByTestId("remove-order-menu")); + await userEvent.click(await screen.findByTestId("remove-button")); + + expect(onRemove).toHaveBeenCalledWith(mockRaindexClient, mockOrder); + expect(onRemoveAndWithdrawAll).not.toHaveBeenCalled(); + }); + + it("calls onRemoveAndWithdrawAll with the order's vaultsList when that dropdown item is clicked", async () => { + mockMatchesAccount.mockReturnValue(true); + const onRemove = vi.fn(); + const onRemoveAndWithdrawAll = vi.fn(); + render(OrderDetail, { + props: { ...defaultProps, onRemove, onRemoveAndWithdrawAll }, + context: new Map([["$$_queryClient", queryClient]]), + }); + + await waitFor(() => { + expect(screen.getByTestId("remove-order-menu")).toBeInTheDocument(); + }); + await userEvent.click(screen.getByTestId("remove-order-menu")); + await userEvent.click( + await screen.findByTestId("remove-and-withdraw-all-button"), + ); + + expect(onRemoveAndWithdrawAll).toHaveBeenCalledWith( + mockRaindexClient, + mockOrder, + mockOrder.vaultsList, + ); + expect(onRemove).not.toHaveBeenCalled(); + }); + + it("does not show the remove dropdown for inactive orders even with onRemoveAndWithdrawAll", async () => { + mockMatchesAccount.mockReturnValue(true); + (mockRaindexClient.getOrderByHash as Mock).mockResolvedValue({ + value: { ...mockOrder, active: false }, + }); + render(OrderDetail, { + props: { ...defaultProps, onRemoveAndWithdrawAll: vi.fn() }, + context: new Map([["$$_queryClient", queryClient]]), + }); + + await waitFor(() => { + expect(screen.getByText("Order")).toBeInTheDocument(); + }); + expect(screen.queryByTestId("remove-order-menu")).not.toBeInTheDocument(); + expect(screen.queryByTestId("remove-button")).not.toBeInTheDocument(); + }); + it("does not show remove button if account does not match owner", async () => { mockMatchesAccount.mockReturnValue(false); diff --git a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte index 5e9d5ef451..d680a4af68 100644 --- a/packages/ui-components/src/lib/components/detail/OrderDetail.svelte +++ b/packages/ui-components/src/lib/components/detail/OrderDetail.svelte @@ -12,7 +12,7 @@ import { QKEY_ORDER } from '../../queries/keys'; import CodeMirrorRainlang from '../CodeMirrorRainlang.svelte'; import { createQuery, useQueryClient } from '@tanstack/svelte-query'; - import { Button, TabItem, Tabs, Tooltip } from 'flowbite-svelte'; + import { Button, Dropdown, DropdownItem, TabItem, Tabs, Tooltip } from 'flowbite-svelte'; import { onDestroy } from 'svelte'; // import OrderApy from '../tables/OrderAPY.svelte'; import type { DebugTradeModalHandler, QuoteDebugModalHandler } from '../../types/modal'; @@ -21,6 +21,7 @@ import { ArrowDownToBracketOutline, ArrowUpFromBracketOutline, + ChevronDownOutline, InfoCircleOutline, WalletOutline } from 'flowbite-svelte-icons'; @@ -51,6 +52,19 @@ */ export let onRemove: (raindexClient: RaindexClient, order: RaindexOrder) => void; + /** Callback function when remove-and-withdraw-all action is triggered for an order. + * Removes the order and withdraws all of its vault balances in a single transaction. + * @param order The order to remove + * @param vaultsList The VaultsList struct containing the order's vaults to withdraw from + */ + export let onRemoveAndWithdrawAll: + | (( + raindexClient: RaindexClient, + order: RaindexOrder, + vaultsList: RaindexVaultsList + ) => void) + | undefined = undefined; + /** Callback function when deposit action is triggered for a vault * @param vault The vault to deposit into */ @@ -142,11 +156,32 @@
{#if matchesAccount(data.owner) && data.active} - + {#if onRemoveAndWithdrawAll} + + + onRemove(raindexClient, data)} + data-testid="remove-button">Remove + onRemoveAndWithdrawAll(raindexClient, data, data.vaultsList)} + data-testid="remove-and-withdraw-all-button" + >Remove and withdraw all vaults + + {:else} + + {/if} {/if} {#if data.active && onTakeOrder}