diff --git a/frontend/.gitignore b/frontend/.gitignore
index 6a56a8d1c..4bd9e15c7 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -29,3 +29,15 @@ GEMINI.md
# Playwright
test-results/
playwright-report/
+
+# Generated lint / test / bundle reports (run locally, never commit)
+lint-output*.txt
+lint.txt
+test-results.txt
+test-output.txt
+test_output.json
+vitest-results*.json
+bundle-stats.html
+
+# Local backups
+*.bak
diff --git a/frontend/README.md b/frontend/README.md
index 5da3cb36c..e0ae6550e 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -38,6 +38,10 @@ npm run test:run
npm run test:e2e
```
+## Generated Reports
+
+Lint, test and bundle reports are **generated locally and must not be committed**. Write them to the git-ignored `test-results/` directory, or delete them before committing. The common artifacts (`lint-output*.txt`, `lint.txt`, `test-results.txt`, `test-output.txt`, `test_output.json`, `vitest-results*.json`, `bundle-stats.html`) are covered by `.gitignore`.
+
## Related Docs
- API docs output: [`docs/api/frontend`](/Users/macbook/stellar/YieldVault-RWA/docs/api/frontend)
diff --git a/frontend/bundle-stats.html b/frontend/bundle-stats.html
deleted file mode 100644
index f21f1996c..000000000
--- a/frontend/bundle-stats.html
+++ /dev/null
@@ -1,4950 +0,0 @@
-
-
-
-
-
-
-
- Rollup Visualizer
-
-
-
-
-
-
-
-
-
diff --git a/frontend/lint-output.txt b/frontend/lint-output.txt
deleted file mode 100644
index 8be0feddf..000000000
Binary files a/frontend/lint-output.txt and /dev/null differ
diff --git a/frontend/lint-output2.txt b/frontend/lint-output2.txt
deleted file mode 100644
index 0c6079594..000000000
--- a/frontend/lint-output2.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-
-> frontend@0.0.0 lint
-> eslint .
-
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\e2e\fixtures.ts
- 199:11 error React Hook "use" is called in function "appPage" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use" react-hooks/rules-of-hooks
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\components\WalletConnect.test.tsx
- 1:46 error 'act' is defined but never used @typescript-eslint/no-unused-vars
- 104:15 error 'flushPromises' is assigned a value but never used @typescript-eslint/no-unused-vars
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\forms\useForm.ts
- 18:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
- 28:41 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\hooks\useUrlState.ts
- 48:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
- 83:47 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\lib\vaultApi.ts
- 97:9 error 'payload' is assigned a value but never used @typescript-eslint/no-unused-vars
- 103:9 error 'payload' is assigned a value but never used @typescript-eslint/no-unused-vars
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\pages\Portfolio.tsx
- 171:6 warning React Hook useEffect has a missing dependency: 'urlState.filters.status'. Either include it or remove the dependency array react-hooks/exhaustive-deps
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\pages\TransactionHistory.test.tsx
- 275:59 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
-
-Γ£û 11 problems (10 errors, 1 warning)
-
diff --git a/frontend/lint-output3.txt b/frontend/lint-output3.txt
deleted file mode 100644
index 7eca4d5fa..000000000
--- a/frontend/lint-output3.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-
-> frontend@0.0.0 lint
-> eslint .
-
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\e2e\fixtures.ts
- 197:3 warning Unused eslint-disable directive (no problems were reported from 'react-hooks/rules-of-hooks')
- 200:11 error React Hook "use" is called in function "appPage" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use" react-hooks/rules-of-hooks
-
-Γ£û 2 problems (1 error, 1 warning)
- 0 errors and 1 warning potentially fixable with the `--fix` option.
-
diff --git a/frontend/lint.txt b/frontend/lint.txt
deleted file mode 100644
index 3764dc173..000000000
--- a/frontend/lint.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-
-> frontend@0.0.0 lint
-> eslint .
-
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\e2e\fixtures.ts
- 199:11 error React Hook "use" is called in function "appPage" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use" react-hooks/rules-of-hooks
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\components\VaultDashboard.tsx
- 61:90 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
- 103:67 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\components\WalletConnect.test.tsx
- 1:46 error 'act' is defined but never used @typescript-eslint/no-unused-vars
- 104:15 error 'flushPromises' is assigned a value but never used @typescript-eslint/no-unused-vars
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\forms\useForm.ts
- 18:44 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
- 28:41 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
-
-C:\Users\HP\Desktop\YieldVault-RWA\frontend\src\hooks\useUrlState.ts
- 48:23 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
- 83:47 error Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
-
-Γ£û 9 problems (9 errors, 0 warnings)
-
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 9df000a83..8d1a2b6f3 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -221,14 +221,6 @@ function AppContent() {
}
/>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
-
-
+
+
+
}
/>
- } />
diff --git a/frontend/src/components/ConfirmationModal.test.tsx b/frontend/src/components/ConfirmationModal.test.tsx
new file mode 100644
index 000000000..4c1eaaba6
--- /dev/null
+++ b/frontend/src/components/ConfirmationModal.test.tsx
@@ -0,0 +1,125 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { useState } from "react";
+import ConfirmationModal from "./ConfirmationModal";
+
+const baseProps = {
+ onClose: vi.fn(),
+ onConfirm: vi.fn(),
+ actionType: "deposit" as const,
+ amount: 100,
+ expectedOutput: 99.5,
+ estimatedFee: 0.5,
+};
+
+function Harness({ isProcessing = false }: { isProcessing?: boolean }) {
+ const [open, setOpen] = useState(false);
+ return (
+
+ setOpen(true)}>
+ open
+
+ {
+ baseProps.onClose();
+ setOpen(false);
+ }}
+ />
+
+ );
+}
+
+const FOCUSABLE_SELECTOR =
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
+
+describe("ConfirmationModal", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("renders nothing while closed", () => {
+ render( );
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+
+ it("renders the dialog with translated copy when open", () => {
+ render( );
+ const dialog = screen.getByRole("dialog");
+ expect(dialog).toHaveAttribute("aria-modal", "true");
+ expect(screen.getByRole("heading", { name: "Confirm Deposit" })).toBeInTheDocument();
+ expect(screen.getByText("Estimated Fee")).toBeInTheDocument();
+ expect(screen.getByText("Total Cost")).toBeInTheDocument();
+ });
+
+ it("shows the large-amount alert for amounts over the threshold", () => {
+ render( );
+ expect(screen.getByRole("alert")).toHaveTextContent("Large transaction amount detected");
+ });
+
+ it("moves focus into the dialog on open", async () => {
+ render( );
+ const dialog = screen.getByRole("dialog");
+ await waitFor(() => {
+ expect(dialog.contains(document.activeElement)).toBe(true);
+ });
+ });
+
+ it("calls onConfirm when the confirm button is clicked", () => {
+ render( );
+ fireEvent.click(screen.getByLabelText("Confirm deposit"));
+ expect(baseProps.onConfirm).toHaveBeenCalledTimes(1);
+ });
+
+ it("closes on Escape", async () => {
+ render( );
+ fireEvent.keyDown(document, { key: "Escape" });
+ await waitFor(() => expect(baseProps.onClose).toHaveBeenCalledTimes(1));
+ });
+
+ it("closes on backdrop click", () => {
+ render( );
+ fireEvent.click(screen.getByRole("dialog"));
+ expect(baseProps.onClose).toHaveBeenCalledTimes(1);
+ });
+
+ it("ignores backdrop clicks while processing", () => {
+ render( );
+ fireEvent.click(screen.getByRole("dialog"));
+ expect(baseProps.onClose).not.toHaveBeenCalled();
+ });
+
+ it("ignores Escape while processing", () => {
+ render( );
+ fireEvent.keyDown(document, { key: "Escape" });
+ expect(baseProps.onClose).not.toHaveBeenCalled();
+ });
+
+ it("does not close when clicking inside the panel", () => {
+ render( );
+ fireEvent.click(screen.getByRole("heading", { name: "Confirm Deposit" }));
+ expect(baseProps.onClose).not.toHaveBeenCalled();
+ });
+
+ it("wraps Tab focus from the last element back to the first", () => {
+ render( );
+ const dialog = screen.getByRole("dialog");
+ const focusables = dialog.querySelectorAll(FOCUSABLE_SELECTOR);
+ const last = focusables[focusables.length - 1];
+ last.focus();
+ fireEvent.keyDown(document, { key: "Tab" });
+ expect(document.activeElement).toBe(focusables[0]);
+ });
+
+ it("restores focus to the trigger after closing", async () => {
+ render( );
+ const trigger = screen.getByTestId("trigger");
+ trigger.focus();
+ fireEvent.click(trigger);
+ await screen.findByRole("dialog");
+ fireEvent.keyDown(document, { key: "Escape" });
+ await waitFor(() => expect(trigger).toHaveFocus());
+ });
+});
diff --git a/frontend/src/components/ConfirmationModal.tsx b/frontend/src/components/ConfirmationModal.tsx
index 852fd5550..c1af35062 100644
--- a/frontend/src/components/ConfirmationModal.tsx
+++ b/frontend/src/components/ConfirmationModal.tsx
@@ -1,6 +1,8 @@
-import React, { useEffect, useRef, useCallback } from "react";
+import React from "react";
import { createPortal } from "react-dom";
import { AlertTriangle, X } from "./icons";
+import { useFocusTrap } from "../hooks/useFocusTrap";
+import { useTranslation } from "../i18n";
interface ConfirmationModalProps {
isOpen: boolean;
@@ -25,65 +27,25 @@ const ConfirmationModal: React.FC = ({
estimatedFee,
isProcessing = false,
}) => {
- const modalRef = useRef(null);
- const previousFocusRef = useRef(null);
+ const { t } = useTranslation();
+ const modalRef = useFocusTrap({
+ active: isOpen,
+ onEscape: onClose,
+ closeOnEscape: !isProcessing,
+ });
const isLargeAmount = amount >= LARGE_AMOUNT_THRESHOLD;
- useEffect(() => {
- if (isOpen) {
- previousFocusRef.current = document.activeElement as HTMLElement;
- requestAnimationFrame(() => {
- const firstInteractive = modalRef.current?.querySelector(
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
- );
- firstInteractive?.focus();
- });
- }
- return () => {
- if (isOpen) {
- previousFocusRef.current?.focus();
- }
- };
- }, [isOpen]);
-
- const handleKeyDown = useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === "Escape" && !isProcessing) {
- onClose();
- return;
- }
- if (event.key !== "Tab" || !modalRef.current) return;
-
- const focusable = modalRef.current.querySelectorAll(
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
- );
- if (focusable.length === 0) {
- event.preventDefault();
- return;
- }
- const firstElement = focusable[0];
- const lastElement = focusable[focusable.length - 1];
- const activeElement = document.activeElement as HTMLElement | null;
-
- if (event.shiftKey && activeElement === firstElement) {
- event.preventDefault();
- lastElement.focus();
- } else if (!event.shiftKey && activeElement === lastElement) {
- event.preventDefault();
- firstElement.focus();
- }
- },
- [isProcessing, onClose]
- );
-
if (!isOpen) return null;
- const title = actionType === "deposit" ? "Confirm Deposit" : "Confirm Withdrawal";
+ const isDeposit = actionType === "deposit";
+ const title = t(isDeposit ? "confirmationModal.depositTitle" : "confirmationModal.withdrawTitle");
const subtitle = isLargeAmount
- ? "This is a large transaction. Please review carefully before proceeding."
- : "Please review your transaction details before confirming.";
- const outputLabel = actionType === "deposit" ? "Shares to Receive" : "USDC to Receive";
+ ? t("confirmationModal.largeAmountSubtitle")
+ : t("confirmationModal.defaultSubtitle");
+ const outputLabel = t(
+ isDeposit ? "confirmationModal.sharesToReceive" : "confirmationModal.usdcToReceive",
+ );
const modalContent = (
= ({
zIndex: 1000,
padding: "16px",
}}
- onClick={onClose}
- onKeyDown={handleKeyDown}
+ onClick={() => {
+ if (!isProcessing) onClose();
+ }}
>
= ({
= ({
>
- Large transaction amount detected
+ {t("confirmationModal.largeAmountAlert")}
)}
@@ -207,7 +170,7 @@ const ConfirmationModal: React.FC
= ({
}}
>
- {actionType === "deposit" ? "Deposit Amount" : "Withdraw Amount"}
+ {t(isDeposit ? "confirmationModal.depositAmount" : "confirmationModal.withdrawAmount")}
= ({
fontFamily: "var(--font-display)",
}}
>
- {expectedOutput.toFixed(6)} {actionType === "deposit" ? "Shares" : "USDC"}
+ {expectedOutput.toFixed(6)} {isDeposit ? "Shares" : "USDC"}
@@ -251,7 +214,7 @@ const ConfirmationModal: React.FC = ({
}}
>
- Estimated Fee
+ {t("confirmationModal.estimatedFee")}
= ({
}}
>
- Total Cost
+ {t("confirmationModal.totalCost")}
= ({
}}
>
- Note:
- {actionType === "deposit"
- ? "Depositing USDC will mint vault shares. This action cannot be undone once confirmed on the Stellar network."
- : "Withdrawing USDC will burn your vault shares. Make sure you have sufficient shares to cover this withdrawal."}
+ {t("confirmationModal.noteLabel")}
+ {t(
+ isDeposit ? "confirmationModal.depositNote" : "confirmationModal.withdrawNote",
+ )}
@@ -316,7 +279,7 @@ const ConfirmationModal: React.FC = ({
= ({
(e.currentTarget as HTMLButtonElement).style.background = "transparent";
}}
>
- Cancel
+ {t("confirmationModal.cancel")}
= ({
(e.currentTarget as HTMLButtonElement).style.opacity = "1";
}}
>
- {isProcessing ? "Processing..." : `Confirm ${actionType === "deposit" ? "Deposit" : "Withdrawal"}`}
+ {isProcessing
+ ? t("confirmationModal.processing")
+ : t(
+ isDeposit
+ ? "confirmationModal.confirmDeposit"
+ : "confirmationModal.confirmWithdrawal",
+ )}
diff --git a/frontend/src/components/Drawer.test.tsx b/frontend/src/components/Drawer.test.tsx
index a869f766f..83d44451d 100644
--- a/frontend/src/components/Drawer.test.tsx
+++ b/frontend/src/components/Drawer.test.tsx
@@ -1,5 +1,6 @@
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+import { useState } from "react";
import { Drawer } from "./Drawer";
describe("Drawer", () => {
@@ -95,4 +96,61 @@ describe("Drawer", () => {
expect(document.activeElement).toBe(focusableElements[0]);
});
+
+ it("restores focus to the trigger after closing", async () => {
+ function Harness() {
+ const [open, setOpen] = useState(false);
+ return (
+ <>
+ setOpen(true)}>
+ open drawer
+
+ setOpen(false)} title="Drawer Title">
+ Inside button
+
+ >
+ );
+ }
+
+ render( );
+ const trigger = screen.getByTestId("drawer-trigger");
+ trigger.focus();
+ fireEvent.click(trigger);
+ await screen.findByRole("dialog");
+
+ fireEvent.keyDown(document, { key: "Escape" });
+
+ await waitFor(() => expect(trigger).toHaveFocus());
+ });
+
+ it("restores focus when the drawer unmounts", async () => {
+ function Harness() {
+ const [mounted, setMounted] = useState(false);
+ return (
+ <>
+ setMounted(true)}>
+ mount
+
+ setMounted(false)}>
+ unmount
+
+ {mounted && (
+ {}} title="Drawer Title">
+ Inside button
+
+ )}
+ >
+ );
+ }
+
+ render( );
+ const mount = screen.getByTestId("mount");
+ mount.focus();
+ fireEvent.click(mount);
+ await screen.findByRole("dialog");
+
+ fireEvent.click(screen.getByTestId("unmount"));
+
+ await waitFor(() => expect(mount).toHaveFocus());
+ });
});
diff --git a/frontend/src/components/Drawer.tsx b/frontend/src/components/Drawer.tsx
index 171b0c69e..41a47f52a 100644
--- a/frontend/src/components/Drawer.tsx
+++ b/frontend/src/components/Drawer.tsx
@@ -1,6 +1,8 @@
-import React, { useEffect, useRef, useCallback } from "react";
+import React from "react";
import { createPortal } from "react-dom";
import { X } from "lucide-react";
+import { useFocusTrap } from "../hooks/useFocusTrap";
+import { useTranslation } from "../i18n";
export interface DrawerProps {
isOpen: boolean;
@@ -16,9 +18,6 @@ export interface DrawerProps {
"aria-describedby"?: string;
}
-const FOCUSABLE_SELECTOR =
- 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
-
export const Drawer: React.FC = ({
isOpen,
onClose,
@@ -32,75 +31,12 @@ export const Drawer: React.FC = ({
"aria-labelledby": ariaLabelledBy,
"aria-describedby": ariaDescribedBy,
}) => {
- const panelRef = useRef(null);
- const previousFocusRef = useRef(null);
-
- const handleKeyDown = useCallback(
- (e: KeyboardEvent) => {
- if (!isOpen) return;
-
- if (e.key === "Escape" && closeOnEscape) {
- onClose();
- return;
- }
-
- if (e.key === "Tab" && panelRef.current) {
- const focusableElements =
- panelRef.current.querySelectorAll(FOCUSABLE_SELECTOR);
-
- if (focusableElements.length === 0) {
- e.preventDefault();
- return;
- }
-
- const firstElement = focusableElements[0];
- const lastElement = focusableElements[focusableElements.length - 1];
-
- if (e.shiftKey) {
- if (
- document.activeElement === firstElement ||
- document.activeElement === panelRef.current
- ) {
- lastElement.focus();
- e.preventDefault();
- }
- } else if (document.activeElement === lastElement) {
- firstElement.focus();
- e.preventDefault();
- }
- }
- },
- [isOpen, onClose, closeOnEscape],
- );
-
- useEffect(() => {
- if (isOpen) {
- previousFocusRef.current = document.activeElement as HTMLElement;
- document.addEventListener("keydown", handleKeyDown);
- document.body.style.overflow = "hidden";
-
- if (panelRef.current) {
- const focusableElements =
- panelRef.current.querySelectorAll(FOCUSABLE_SELECTOR);
- if (focusableElements.length > 0) {
- focusableElements[0].focus();
- } else {
- panelRef.current.focus();
- }
- }
- } else {
- document.removeEventListener("keydown", handleKeyDown);
- document.body.style.overflow = "";
- if (previousFocusRef.current) {
- previousFocusRef.current.focus();
- }
- }
-
- return () => {
- document.removeEventListener("keydown", handleKeyDown);
- document.body.style.overflow = "";
- };
- }, [isOpen, handleKeyDown]);
+ const { t } = useTranslation();
+ const panelRef = useFocusTrap({
+ active: isOpen,
+ onEscape: onClose,
+ closeOnEscape,
+ });
if (!isOpen) return null;
@@ -146,7 +82,7 @@ export const Drawer: React.FC = ({
diff --git a/frontend/src/components/ToastCenter.test.tsx b/frontend/src/components/ToastCenter.test.tsx
new file mode 100644
index 000000000..ec5c44982
--- /dev/null
+++ b/frontend/src/components/ToastCenter.test.tsx
@@ -0,0 +1,67 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import type { ToastItem } from "../context/ToastContext";
+import ToastCenter from "./ToastCenter";
+
+const { mockUseToast } = vi.hoisted(() => ({ mockUseToast: vi.fn() }));
+
+vi.mock("../context/ToastContext", () => ({
+ useToast: () => mockUseToast(),
+}));
+
+const sampleToast: ToastItem = {
+ id: "toast-1",
+ variant: "success",
+ title: "Deposit confirmed",
+ description: "Your shares are on the way",
+ duration: 5000,
+ timestamp: 0,
+};
+
+function setToasts(toasts: ToastItem[] = []) {
+ const dismissToast = vi.fn();
+ mockUseToast.mockReturnValue({ toasts, dismissToast });
+ return { dismissToast };
+}
+
+describe("ToastCenter", () => {
+ beforeEach(() => {
+ mockUseToast.mockReset();
+ setToasts();
+ });
+
+ it("opens the notification panel and moves focus into it", async () => {
+ render( );
+ const toggle = screen.getByRole("button", { name: /notifications/i });
+ fireEvent.click(toggle);
+ const panel = await screen.findByRole("dialog");
+ await waitFor(() => expect(panel.contains(document.activeElement)).toBe(true));
+ });
+
+ it("closes on Escape and restores focus to the toggle", async () => {
+ render( );
+ const toggle = screen.getByRole("button", { name: /notifications/i });
+ toggle.focus();
+ fireEvent.click(toggle);
+ await screen.findByRole("dialog");
+ fireEvent.keyDown(document, { key: "Escape" });
+ await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
+ await waitFor(() => expect(toggle).toHaveFocus());
+ });
+
+ it("does not lock page scroll, since it is a non-modal popover", async () => {
+ render( );
+ fireEvent.click(screen.getByRole("button", { name: /notifications/i }));
+ await screen.findByRole("dialog");
+ expect(document.body.style.overflow).toBe("");
+ });
+
+ it("renders queued toasts and dismisses them by id", () => {
+ const { dismissToast } = setToasts([sampleToast]);
+ render( );
+ fireEvent.click(screen.getByRole("button", { name: /notifications/i }));
+ expect(screen.getByText("Deposit confirmed")).toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: "Dismiss Deposit confirmed" }));
+ expect(dismissToast).toHaveBeenCalledWith("toast-1");
+ });
+});
diff --git a/frontend/src/components/ToastCenter.tsx b/frontend/src/components/ToastCenter.tsx
index 734fab824..edb601e44 100644
--- a/frontend/src/components/ToastCenter.tsx
+++ b/frontend/src/components/ToastCenter.tsx
@@ -2,6 +2,8 @@ import React, { useState } from "react";
import { Bell, X } from "lucide-react";
import type { ToastItem } from "../context/ToastContext";
import { useToast } from "../context/ToastContext";
+import { useFocusTrap } from "../hooks/useFocusTrap";
+import { useTranslation } from "../i18n";
function groupToasts(toasts: ToastItem[]): (ToastItem & { count: number })[] {
const groups = new Map();
@@ -18,8 +20,16 @@ function groupToasts(toasts: ToastItem[]): (ToastItem & { count: number })[] {
}
const ToastCenter: React.FC = () => {
+ const { t } = useTranslation();
const { toasts, dismissToast } = useToast();
const [isOpen, setIsOpen] = useState(false);
+ const panelRef = useFocusTrap({
+ active: isOpen,
+ onEscape: () => setIsOpen(false),
+ closeOnEscape: true,
+ trapTab: false,
+ lockScroll: false,
+ });
const grouped = groupToasts(toasts);
return (
@@ -28,7 +38,7 @@ const ToastCenter: React.FC = () => {
type="button"
className="toast-center-toggle"
onClick={() => setIsOpen((o) => !o)}
- aria-label={`Notifications (${toasts.length})`}
+ aria-label={t("toastCenter.toggleLabel", { count: toasts.length })}
style={{
position: "fixed",
bottom: "20px",
@@ -73,9 +83,10 @@ const ToastCenter: React.FC = () => {
{isOpen && (
{
}}
>
- Notifications
+ {t("toastCenter.heading")}
setIsOpen(false)}
- aria-label="Close notification center"
+ aria-label={t("toastCenter.closeLabel")}
style={{
background: "none",
border: "none",
@@ -120,7 +131,7 @@ const ToastCenter: React.FC = () => {
{grouped.length === 0 ? (
- No notifications
+ {t("toastCenter.empty")}
) : (
grouped.map((g) => (
@@ -174,7 +185,7 @@ const ToastCenter: React.FC = () => {
dismissToast(g.id)}
- aria-label={`Dismiss ${g.title}`}
+ aria-label={t("toastCenter.dismissLabel", { title: g.title })}
style={{
background: "none",
border: "none",
diff --git a/frontend/src/components/WithdrawalConfirmationModal.test.tsx b/frontend/src/components/WithdrawalConfirmationModal.test.tsx
new file mode 100644
index 000000000..e6721aa8f
--- /dev/null
+++ b/frontend/src/components/WithdrawalConfirmationModal.test.tsx
@@ -0,0 +1,118 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { useState } from "react";
+import WithdrawalConfirmationModal from "./WithdrawalConfirmationModal";
+
+const baseProps = {
+ amount: 100,
+ estimatedFee: 0.5,
+ onConfirm: vi.fn(),
+ onCancel: vi.fn(),
+};
+
+function Harness({ isProcessing = false }: { isProcessing?: boolean }) {
+ const [open, setOpen] = useState(false);
+ return (
+
+ setOpen(true)}>
+ open
+
+ {
+ baseProps.onCancel();
+ setOpen(false);
+ }}
+ />
+
+ );
+}
+
+const FOCUSABLE_SELECTOR =
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
+
+describe("WithdrawalConfirmationModal", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("renders nothing while closed", () => {
+ render( );
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ });
+
+ it("renders the dialog with translated copy when open", () => {
+ render( );
+ const dialog = screen.getByRole("dialog");
+ expect(dialog).toHaveAttribute("aria-modal", "true");
+ expect(screen.getByRole("heading", { name: "Confirm Withdrawal" })).toBeInTheDocument();
+ expect(screen.getByText("Estimated Network Fee")).toBeInTheDocument();
+ expect(screen.getByText("Total Deducted")).toBeInTheDocument();
+ });
+
+ it("moves focus into the dialog on open", async () => {
+ render( );
+ const dialog = screen.getByRole("dialog");
+ await waitFor(() => {
+ expect(dialog.contains(document.activeElement)).toBe(true);
+ });
+ });
+
+ it("calls onConfirm when the confirm button is clicked", () => {
+ render( );
+ fireEvent.click(screen.getByRole("button", { name: "Confirm Withdrawal" }));
+ expect(baseProps.onConfirm).toHaveBeenCalledTimes(1);
+ });
+
+ it("closes via onCancel on Escape", async () => {
+ render( );
+ fireEvent.keyDown(document, { key: "Escape" });
+ await waitFor(() => expect(baseProps.onCancel).toHaveBeenCalledTimes(1));
+ });
+
+ it("closes on backdrop click when idle", () => {
+ render( );
+ fireEvent.click(screen.getByRole("dialog"));
+ expect(baseProps.onCancel).toHaveBeenCalledTimes(1);
+ });
+
+ it("ignores backdrop clicks while processing", () => {
+ render( );
+ fireEvent.click(screen.getByRole("dialog"));
+ expect(baseProps.onCancel).not.toHaveBeenCalled();
+ });
+
+ it("ignores Escape while processing", () => {
+ render( );
+ fireEvent.keyDown(document, { key: "Escape" });
+ expect(baseProps.onCancel).not.toHaveBeenCalled();
+ });
+
+ it("does not close when clicking inside the panel", () => {
+ render( );
+ fireEvent.click(screen.getByRole("heading", { name: "Confirm Withdrawal" }));
+ expect(baseProps.onCancel).not.toHaveBeenCalled();
+ });
+
+ it("wraps Tab focus from the last element back to the first", () => {
+ render( );
+ const dialog = screen.getByRole("dialog");
+ const focusables = dialog.querySelectorAll(FOCUSABLE_SELECTOR);
+ const last = focusables[focusables.length - 1];
+ last.focus();
+ fireEvent.keyDown(document, { key: "Tab" });
+ expect(document.activeElement).toBe(focusables[0]);
+ });
+
+ it("restores focus to the trigger after closing", async () => {
+ render( );
+ const trigger = screen.getByTestId("trigger");
+ trigger.focus();
+ fireEvent.click(trigger);
+ await screen.findByRole("dialog");
+ fireEvent.keyDown(document, { key: "Escape" });
+ await waitFor(() => expect(trigger).toHaveFocus());
+ });
+});
diff --git a/frontend/src/components/WithdrawalConfirmationModal.tsx b/frontend/src/components/WithdrawalConfirmationModal.tsx
index 438decbdf..a03ba758f 100644
--- a/frontend/src/components/WithdrawalConfirmationModal.tsx
+++ b/frontend/src/components/WithdrawalConfirmationModal.tsx
@@ -1,6 +1,7 @@
import React from "react";
import { X } from "./icons";
import { useTranslation } from "../i18n";
+import { useFocusTrap } from "../hooks/useFocusTrap";
interface WithdrawalConfirmationModalProps {
isOpen: boolean;
@@ -20,6 +21,11 @@ const WithdrawalConfirmationModal: React.FC =
isProcessing = false,
}) => {
const { t } = useTranslation();
+ const panelRef = useFocusTrap({
+ active: isOpen,
+ onEscape: onCancel,
+ closeOnEscape: !isProcessing,
+ });
if (!isOpen) return null;
@@ -27,6 +33,9 @@ const WithdrawalConfirmationModal: React.FC =
return (
=
zIndex: 1000,
padding: "16px",
}}
- onClick={onCancel}
+ onClick={() => {
+ if (!isProcessing) onCancel();
+ }}
>
=
=
{/* Header */}
void;
closeOnEscape?: boolean;
+ /**
+ * Trap Tab focus inside the container. Non-modal surfaces (for example a
+ * notification popover) set this to `false` so keyboard users can still Tab
+ * back out to the page behind.
+ */
+ trapTab?: boolean;
+ /** Lock page scroll behind the surface while active. */
+ lockScroll?: boolean;
}
/**
@@ -29,6 +37,8 @@ export function useFocusTrap({
active,
onEscape,
closeOnEscape = true,
+ trapTab = true,
+ lockScroll = true,
}: UseFocusTrapOptions) {
const containerRef = useRef(null);
const previousFocusRef = useRef(null);
@@ -42,7 +52,7 @@ export function useFocusTrap({
return;
}
- if (event.key !== "Tab" || !containerRef.current) return;
+ if (event.key !== "Tab" || !containerRef.current || !trapTab) return;
const focusable = getFocusable(containerRef.current);
if (focusable.length === 0) {
@@ -64,7 +74,7 @@ export function useFocusTrap({
event.preventDefault();
}
},
- [active, closeOnEscape, onEscape],
+ [active, closeOnEscape, onEscape, trapTab],
);
useEffect(() => {
@@ -72,7 +82,7 @@ export function useFocusTrap({
previousFocusRef.current = document.activeElement as HTMLElement;
document.addEventListener("keydown", handleKeyDown);
- document.body.style.overflow = "hidden";
+ if (lockScroll) document.body.style.overflow = "hidden";
const frame = requestAnimationFrame(() => {
const container = containerRef.current;
@@ -88,10 +98,10 @@ export function useFocusTrap({
return () => {
cancelAnimationFrame(frame);
document.removeEventListener("keydown", handleKeyDown);
- document.body.style.overflow = "";
+ if (lockScroll) document.body.style.overflow = "";
previousFocusRef.current?.focus();
};
- }, [active, handleKeyDown]);
+ }, [active, handleKeyDown, lockScroll]);
return containerRef;
}
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 6fc01f5df..27d08690e 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -382,6 +382,40 @@ export const en = {
processing: "Processing...",
confirm: "Confirm Withdrawal",
},
+ confirmationModal: {
+ depositTitle: "Confirm Deposit",
+ withdrawTitle: "Confirm Withdrawal",
+ largeAmountSubtitle: "This is a large transaction. Please review carefully before proceeding.",
+ defaultSubtitle: "Please review your transaction details before confirming.",
+ sharesToReceive: "Shares to Receive",
+ usdcToReceive: "USDC to Receive",
+ closeLabel: "Close confirmation dialog",
+ largeAmountAlert: "Large transaction amount detected",
+ depositAmount: "Deposit Amount",
+ withdrawAmount: "Withdraw Amount",
+ estimatedFee: "Estimated Fee",
+ totalCost: "Total Cost",
+ noteLabel: "Note:",
+ depositNote: "Depositing USDC will mint vault shares. This action cannot be undone once confirmed on the Stellar network.",
+ withdrawNote: "Withdrawing USDC will burn your vault shares. Make sure you have sufficient shares to cover this withdrawal.",
+ cancel: "Cancel",
+ cancelLabel: "Cancel transaction",
+ confirmLabel: "Confirm {{action}}",
+ processing: "Processing...",
+ confirmDeposit: "Confirm Deposit",
+ confirmWithdrawal: "Confirm Withdrawal",
+ },
+ drawer: {
+ closeLabel: "Close drawer",
+ },
+ toastCenter: {
+ panelLabel: "Notification center",
+ heading: "Notifications",
+ closeLabel: "Close notification center",
+ empty: "No notifications",
+ toggleLabel: "Notifications ({{count}})",
+ dismissLabel: "Dismiss {{title}}",
+ },
settings: {
title: "Settings",
subtitle: "Manage your preferences — they sync across sessions automatically.",
diff --git a/frontend/src/i18n/locales/es.ts b/frontend/src/i18n/locales/es.ts
index 36366823b..82bf6a511 100644
--- a/frontend/src/i18n/locales/es.ts
+++ b/frontend/src/i18n/locales/es.ts
@@ -356,6 +356,40 @@ export const es = {
processing: "Procesando...",
confirm: "Confirmar retiro",
},
+ confirmationModal: {
+ depositTitle: "Confirmar depósito",
+ withdrawTitle: "Confirmar retiro",
+ largeAmountSubtitle: "Esta es una transacción grande. Revísala con cuidado antes de continuar.",
+ defaultSubtitle: "Revisa los detalles de tu transacción antes de confirmar.",
+ sharesToReceive: "Acciones a recibir",
+ usdcToReceive: "USDC a recibir",
+ closeLabel: "Cerrar diálogo de confirmación",
+ largeAmountAlert: "Se detectó un monto de transacción grande",
+ depositAmount: "Monto del depósito",
+ withdrawAmount: "Monto a retirar",
+ estimatedFee: "Tarifa estimada",
+ totalCost: "Costo total",
+ noteLabel: "Nota:",
+ depositNote: "Depositar USDC acuñará acciones de la bóveda. Esta acción no se puede deshacer una vez confirmada en la red Stellar.",
+ withdrawNote: "Retirar USDC quemará tus acciones de la bóveda. Asegúrate de tener suficientes acciones para cubrir este retiro.",
+ cancel: "Cancelar",
+ cancelLabel: "Cancelar transacción",
+ confirmLabel: "Confirmar {{action}}",
+ processing: "Procesando...",
+ confirmDeposit: "Confirmar depósito",
+ confirmWithdrawal: "Confirmar retiro",
+ },
+ drawer: {
+ closeLabel: "Cerrar panel",
+ },
+ toastCenter: {
+ panelLabel: "Centro de notificaciones",
+ heading: "Notificaciones",
+ closeLabel: "Cerrar centro de notificaciones",
+ empty: "Sin notificaciones",
+ toggleLabel: "Notificaciones ({{count}})",
+ dismissLabel: "Descartar {{title}}",
+ },
settings: {
title: "Configuración",
subtitle: "Gestiona tus preferencias — se sincronizan automáticamente entre sesiones.",
diff --git a/frontend/src/tests/routes.test.tsx b/frontend/src/tests/routes.test.tsx
new file mode 100644
index 000000000..c14c91f1a
--- /dev/null
+++ b/frontend/src/tests/routes.test.tsx
@@ -0,0 +1,133 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, waitFor } from "@testing-library/react";
+import { MemoryRouter } from "react-router-dom";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import type { ReactNode } from "react";
+import App from "../App";
+
+vi.mock("../pages/Home", () => ({
+ default: () => Home
,
+}));
+
+vi.mock("../pages/Portfolio", () => ({
+ default: () => Portfolio
,
+}));
+
+vi.mock("../pages/TransactionHistory", () => ({
+ default: () => Transactions
,
+}));
+
+vi.mock("../pages/VaultComparison", () => ({
+ default: () => Compare
,
+}));
+
+vi.mock("../pages/Settings", () => ({
+ default: () => Settings
,
+}));
+
+vi.mock("../pages/UIPreview", () => ({
+ default: () => UI Kit
,
+}));
+
+vi.mock("../pages/StrategyDetail", () => ({
+ default: () => Strategy Detail
,
+}));
+
+vi.mock("../pages/TransactionReceipt", () => ({
+ default: () => Receipt
,
+}));
+
+vi.mock("../pages/VaultHealthDashboard", () => ({
+ default: () => Vault Health
,
+}));
+
+vi.mock("../pages/AuditLog", () => ({
+ default: () => Audit Log
,
+}));
+
+vi.mock("../components/Navbar", () => ({
+ default: () => Navbar
,
+}));
+
+vi.mock("../components/ShortcutHelpModal", () => ({ default: () => null }));
+vi.mock("../components/CommandPalette", () => ({ default: () => null }));
+vi.mock("../components/OnboardingWalkthrough", () => ({ default: () => null }));
+vi.mock("../components/ToastCenter", () => ({ default: () => null }));
+vi.mock("../components/OfflineBanner", () => ({ default: () => null }));
+vi.mock("../components/NetworkWarningBanner", () => ({ default: () => null }));
+vi.mock("../components/HighLatencyBanner", () => ({ default: () => null }));
+
+vi.mock("@sentry/react", () => ({
+ withSentryReactRouterV6Routing: (comp: T) => comp,
+ ErrorBoundary: ({ children }: { children: ReactNode }) => <>{children}>,
+ init: vi.fn(),
+}));
+
+vi.mock("../i18n", () => ({
+ useTranslation: () => ({ t: (key: string) => key }),
+}));
+
+vi.mock("../hooks/useBalanceData", () => ({
+ useUsdcBalance: () => ({ data: 1000, isLoading: false }),
+ useXlmBalance: () => ({ data: 10.0, isLoading: false }),
+}));
+
+vi.mock("../hooks/useVaultData", () => ({
+ useVaultSummary: () => ({ data: undefined, isLoading: false, error: null, refetch: vi.fn() }),
+ useVaultHistory: () => ({ data: [], isLoading: false, error: null, refetch: vi.fn() }),
+}));
+
+vi.mock("../hooks/useAnalytics", () => ({
+ usePageViewTracking: () => undefined,
+}));
+
+function renderAt(path: string) {
+ const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+ return render(
+
+
+
+
+ ,
+ );
+}
+
+describe("route table smoke test", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ // Each path must resolve to exactly one route definition: this guards the
+ // #1564 deduplication, where duplicate entries could shadow one
+ // another depending on ordering.
+ const routes: Array<[string, string]> = [
+ ["/", "route-home"],
+ ["/portfolio", "route-portfolio"],
+ ["/transactions", "route-transactions"],
+ ["/compare", "route-compare"],
+ ["/settings", "route-settings"],
+ ["/vault-health", "route-vault-health"],
+ ["/audit-log", "route-audit-log"],
+ ["/ui-kit", "route-ui-kit"],
+ ["/strategies/vault-1", "route-strategy"],
+ ["/receipt/abc123", "route-receipt"],
+ ];
+
+ it.each(routes)("deep-links %s to a single route", async (path, testId) => {
+ renderAt(path);
+
+ await waitFor(() => {
+ expect(screen.getByTestId(testId)).toBeInTheDocument();
+ });
+
+ expect(screen.getAllByTestId(testId)).toHaveLength(1);
+ });
+
+ it("renders the not-found boundary and redirects unknown paths home", async () => {
+ renderAt("/this-route-does-not-exist");
+
+ await waitFor(() => {
+ expect(screen.getByTestId("route-home")).toBeInTheDocument();
+ });
+ });
+});
diff --git a/frontend/test-output.txt b/frontend/test-output.txt
deleted file mode 100644
index 843dc8820..000000000
--- a/frontend/test-output.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-
-> frontend@0.0.0 test:run
-> vitest --run
-
-sh: 1: vitest: not found
diff --git a/frontend/test-results.txt b/frontend/test-results.txt
deleted file mode 100644
index 68220465b..000000000
--- a/frontend/test-results.txt
+++ /dev/null
@@ -1 +0,0 @@
-sh: 1: vitest: not found
diff --git a/frontend/test_output.json b/frontend/test_output.json
deleted file mode 100644
index 7085810b0..000000000
Binary files a/frontend/test_output.json and /dev/null differ
diff --git a/frontend/vitest-results.json b/frontend/vitest-results.json
deleted file mode 100644
index c3b91c9a1..000000000
--- a/frontend/vitest-results.json
+++ /dev/null
@@ -1,2 +0,0 @@
-[sentry-vite-plugin] Info: Sending telemetry data on issues and performance to Sentry. To disable telemetry, set `options.telemetry` to `false`.
-{"numTotalTestSuites":36,"numPassedTestSuites":30,"numFailedTestSuites":6,"numPendingTestSuites":0,"numTotalTests":106,"numPassedTests":100,"numFailedTests":6,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1774558015885,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Navbar"],"fullName":"Navbar renders the navbar with navigation links","status":"passed","title":"renders the navbar with navigation links","duration":287.96609999999964,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Navbar"],"fullName":"Navbar renders the wallet connect button","status":"passed","title":"renders the wallet connect button","duration":49.33969999999863,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Navbar"],"fullName":"Navbar shows the truncated wallet address when connected","status":"passed","title":"shows the truncated wallet address when connected","duration":59.53660000000127,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558034402,"endTime":1774558034799.5366,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/Navbar.test.tsx"},{"assertionResults":[{"ancestorTitles":["ThemeToggle"],"fullName":"ThemeToggle renders the theme toggle button","status":"passed","title":"renders the theme toggle button","duration":720.5275000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ThemeToggle"],"fullName":"ThemeToggle changes the theme when clicked","status":"passed","title":"changes the theme when clicked","duration":130.97060000000056,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558024903,"endTime":1774558025753.9707,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/ThemeToggle.test.tsx"},{"assertionResults":[{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard renders the connect overlay when wallet is not connected","status":"passed","title":"renders the connect overlay when wallet is not connected","duration":543.6220000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard renders the dashboard when wallet is connected","status":"passed","title":"renders the dashboard when wallet is connected","duration":150.65369999999893,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard allows switching between deposit and withdraw tabs","status":"passed","title":"allows switching between deposit and withdraw tabs","duration":187.93459999999868,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard updates the amount input and processes a deposit","status":"failed","title":"updates the amount input and processes a deposit","duration":386.9326000000001,"failureMessages":["Error: \u001b[2mexpect(\u001b[22m\u001b[31melement\u001b[39m\u001b[2m).not.toBeInTheDocument()\u001b[22m\n\n\u001b[31mexpected document not to contain element, found \n Processing Transaction...\n instead\u001b[39m\n at Proxy.expectWrapper (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/expect/dist/index.js:1882:12)\n at Proxy. (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/expect/dist/index.js:1155:15)\n at Proxy.methodWrapper (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/chai/index.js:1700:25)\n at C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/VaultDashboard.test.tsx:120:11\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1893:20"],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard shows a normalized API error message when data loading fails","status":"passed","title":"shows a normalized API error message when data loading fails","duration":1451.6965,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558028613,"endTime":1774558031334.6965,"status":"failed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/VaultDashboard.test.tsx"},{"assertionResults":[{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect renders the connect button when no wallet is connected","status":"passed","title":"renders the connect button when no wallet is connected","duration":168.3107,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect calls onConnect when manually connected via button","status":"passed","title":"calls onConnect when manually connected via button","duration":68.8127999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect shows the formatted address when connected","status":"passed","title":"shows the formatted address when connected","duration":56.05449999999837,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect calls onDisconnect when the disconnect button is clicked","status":"passed","title":"calls onDisconnect when the disconnect button is clicked","duration":34.47199999999975,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect handles wallet disconnects gracefully during polling","status":"passed","title":"handles wallet disconnects gracefully during polling","duration":18.457099999999627,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558034539,"endTime":1774558034885.457,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/WalletConnect.test.tsx"},{"assertionResults":[{"ancestorTitles":["ToastProvider"],"fullName":"ToastProvider shows and auto-dismisses toasts","status":"passed","title":"shows and auto-dismisses toasts","duration":754.0685000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ToastProvider"],"fullName":"ToastProvider supports manual dismissal","status":"passed","title":"supports manual dismissal","duration":160.45460000000094,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558024330,"endTime":1774558025244.4546,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/context/ToastContext.test.tsx"},{"assertionResults":[{"ancestorTitles":["useForm"],"fullName":"useForm returns initial state","status":"passed","title":"returns initial state","duration":77.12700000000041,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleChange updates values","status":"passed","title":"handleChange updates values","duration":10.769800000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleBlur marks touched and validates field","status":"passed","title":"handleBlur marks touched and validates field","duration":9.869499999999789,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleSubmit validates all fields and blocks submit when invalid","status":"passed","title":"handleSubmit validates all fields and blocks submit when invalid","duration":16.84859999999935,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm tracks isSubmitting lifecycle for valid submit","status":"passed","title":"tracks isSubmitting lifecycle for valid submit","duration":32.610999999999876,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm setFieldError stores server-side error","status":"passed","title":"setFieldError stores server-side error","duration":7.341199999999844,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558033051,"endTime":1774558033206.3413,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/useForm.test.tsx"},{"assertionResults":[{"ancestorTitles":["validate"],"fullName":"validate fails when required field is missing","status":"passed","title":"fails when required field is missing","duration":6.921400000000176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails min length and passes when min is met","status":"passed","title":"fails min length and passes when min is met","duration":1.4904999999998836,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails max length and passes when within max","status":"passed","title":"fails max length and passes when within max","duration":0.9960000000000946,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails pattern and passes matching pattern","status":"passed","title":"fails pattern and passes matching pattern","duration":0.7665999999999258,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate runs custom validator","status":"passed","title":"runs custom validator","duration":0.623000000000502,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558037289,"endTime":1774558037300.623,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/validate.test.ts"},{"assertionResults":[{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount returns null when Freighter is not allowed","status":"passed","title":"returns null when Freighter is not allowed","duration":5.859400000000278,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount returns discovered address when Freighter is allowed","status":"passed","title":"returns discovered address when Freighter is allowed","duration":0.9382999999997992,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount extracts USDC balance from account balances","status":"passed","title":"extracts USDC balance from account balances","duration":1.1228000000000975,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558036645,"endTime":1774558036653.1228,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/stellarAccount.test.ts"},{"assertionResults":[{"ancestorTitles":["Portfolio"],"fullName":"Portfolio shows the wallet prompt when disconnected","status":"passed","title":"shows the wallet prompt when disconnected","duration":228.5676000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio renders holdings in the reusable table","status":"failed","title":"renders holdings in the reusable table","duration":1266.179900000001,"failureMessages":["Error: Unable to find an element with the text: /Tokenized T-Bills/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mData unavailable\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mInvalid value for \"walletAddress\": Must be a valid Stellar public key (starts with G, 56 chars).\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTotal Assets\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mUnrealized Gain\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m+\u001b[0m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mHoldings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort, search, and page through all current vault positions.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStatus Filter\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll Statuses\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mActive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInactive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSearch holdings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 holdings found\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
)\n at runWithCancel (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2313:10)\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2295:20\n at new Promise ()"],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio persists filter state in the URL","status":"failed","title":"persists filter state in the URL","duration":1227.9897999999994,"failureMessages":["Error: Unable to find an element with the text: /Tokenized T-Bills/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mData unavailable\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mInvalid value for \"walletAddress\": Must be a valid Stellar public key (starts with G, 56 chars).\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTotal Assets\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mUnrealized Gain\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m+\u001b[0m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mHoldings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort, search, and page through all current vault positions.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStatus Filter\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll Statuses\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mActive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInactive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSearch holdings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mReset Filters\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 holdings found\u001b[...\n at waitForWrapper (C:\\Users\\HP\\Desktop\\YieldVault-RWA\\frontend\\node_modules\\@testing-library\\dom\\dist\\wait-for.js:163:27)\n at C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/Portfolio.test.tsx:151:11\n at runNextTicks (node:internal/process/task_queues:65:5)\n at processTimers (node:internal/timers:526:9)\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1893:20"],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio supports keyboard sorting and pagination state from the URL","status":"failed","title":"supports keyboard sorting and pagination state from the URL","duration":1122.061099999999,"failureMessages":["Error: Unable to find an element with the text: /Yield Bearing Cash/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mData unavailable\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mInvalid value for \"walletAddress\": Must be a valid Stellar public key (starts with G, 56 chars).\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTotal Assets\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mUnrealized Gain\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m+\u001b[0m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mHoldings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort, search, and page through all current vault positions.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStatus Filter\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll Statuses\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mActive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInactive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSearch holdings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 holdings found\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
)\n at runWithCancel (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2313:10)\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2295:20\n at new Promise ()"],"meta":{},"tags":[]}],"startTime":1774558026135,"endTime":1774558029980.061,"status":"failed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/Portfolio.test.tsx"},{"assertionResults":[{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders connect-wallet prompt when walletAddress is null","status":"passed","title":"renders connect-wallet prompt when walletAddress is null","duration":208.9276000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows loading indicator while fetch is pending","status":"passed","title":"shows loading indicator while fetch is pending","duration":181.09439999999995,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory calls getTransactions with the correct wallet address on mount","status":"failed","title":"calls getTransactions with the correct wallet address on mount","duration":1110.4812000000002,"failureMessages":["AssertionError: expected \"vi.fn()\" to be called with arguments: [ 'GABC1234567890TESTWALLETADDRESS' ]\u001b[90m\n\nReceived:\n\n\u001b[1m 1st vi.fn() call:\n\n\u001b[22m\u001b[2m [\u001b[22m\n\u001b[32m- \"GABC1234567890TESTWALLETADDRESS\",\u001b[90m\n\u001b[31m+ {\u001b[90m\n\u001b[31m+ \"limit\": 10,\u001b[90m\n\u001b[31m+ \"order\": \"desc\",\u001b[90m\n\u001b[31m+ \"type\": \"all\",\u001b[90m\n\u001b[31m+ \"walletAddress\": \"GABC1234567890TESTWALLETADDRESS\",\u001b[90m\n\u001b[31m+ },\u001b[90m\n\u001b[2m ]\u001b[22m\n\u001b[39m\u001b[90m\n\nNumber of calls: \u001b[1m1\u001b[22m\n\u001b[39m\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTransactions\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort and filter your deposit and withdrawal history.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mType\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDeposit\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mWithdrawal\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mRows\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m10\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m25\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m50\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 transactions found\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mTransaction history\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mType\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mΓåò\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAmount\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mΓåò\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAsset\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDate\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mΓåô\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mTransaction Hash\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mNo transactions found for this wallet.\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mPage 1 of 1\u001b[0m\n \u001b[0m ΓÇö Showing \u001b[0m\n \u001b[0m0\u001b[0m\n \u001b[0mΓÇô\u001b[0m\n \u001b[0m0\u001b[0m\n \u001b[0m of \u001b[0m\n \u001b[0m0\u001b[0m\n \u001b[0m results\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n\u001b[36m\u001b[39m\n at Proxy. (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/expect/dist/index.js:1486:10)\n at Proxy. (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/expect/dist/index.js:1155:15)\n at Proxy.methodWrapper (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/chai/index.js:1700:25)\n at C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/TransactionHistory.test.tsx:101:35\n at runWithExpensiveErrorDiagnosticsDisabled (C:\\Users\\HP\\Desktop\\YieldVault-RWA\\frontend\\node_modules\\@testing-library\\dom\\dist\\config.js:47:12)\n at checkCallback (C:\\Users\\HP\\Desktop\\YieldVault-RWA\\frontend\\node_modules\\@testing-library\\dom\\dist\\wait-for.js:124:77)\n at Timeout.checkRealTimersCallback (C:\\Users\\HP\\Desktop\\YieldVault-RWA\\frontend\\node_modules\\@testing-library\\dom\\dist\\wait-for.js:118:16)\n at listOnTimeout (node:internal/timers:594:17)\n at processTimers (node:internal/timers:529:7)"],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders the transaction table when wallet is connected and fetch succeeds","status":"passed","title":"renders the transaction table when wallet is connected and fetch succeeds","duration":785.9304000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows ApiStatusBanner on fetch failure","status":"passed","title":"shows ApiStatusBanner on fetch failure","duration":255.60270000000128,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders correct column headers: Type, Amount, Asset, Date, Transaction Hash","status":"passed","title":"renders correct column headers: Type, Amount, Asset, Date, Transaction Hash","duration":468.1130000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory has sort buttons for Type, Amount, Date but not for Asset and Transaction Hash","status":"passed","title":"has sort buttons for Type, Amount, Date but not for Asset and Transaction Hash","duration":366.6665000000012,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory default page size select shows 10","status":"passed","title":"default page size select shows 10","duration":244.6980999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders filter control with All, Deposit, and Withdrawal options","status":"passed","title":"renders filter control with All, Deposit, and Withdrawal options","duration":208.63750000000073,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory resets page to 1 when filter is applied","status":"passed","title":"resets page to 1 when filter is applied","duration":688.8478000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders deposit badge with 'cyan' class and withdrawal badge with 'red' class","status":"passed","title":"renders deposit badge with 'cyan' class and withdrawal badge with 'red' class","duration":153.87179999999898,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows empty state message when wallet is connected but no transactions exist","status":"passed","title":"shows empty state message when wallet is connected but no transactions exist","duration":91.08209999999963,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows filtered empty state message when filter yields no results","status":"failed","title":"shows filtered empty state message when filter yields no results","duration":1335.3719,"failureMessages":["Error: Unable to find an element with the text: No transactions matched the current filter.. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTransactions\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort and filter your deposit and withdrawal history.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mType\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDeposit\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mWithdrawal\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mRows\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m10\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m25\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m50\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 transactions found\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mTransaction history\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mType\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mΓåò\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAmount\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mΓåò\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAsset\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mDate\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mΓåô\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mTransaction Hash\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mdeposit\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m100.00 USDC\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mUSDC\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mJan 15, 2025, 11:30 AM\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mabcdef12...ef12\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mPage 1 of 1\u001b[0m\n \u001b[0m ΓÇö Showing \u001b[0m\n \u001b[0m1\u001b[0m\n \u001b[0mΓÇô\u001b[0m\n \u001b[0m1\u001b[0m\n \u001b[0m of \u001b[0m\n \u001b[0m1\u001b[0m\n \u001b[0m results\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n\u001b[36m\u001b[39m\n at Proxy.waitForWrapper (C:\\Users\\HP\\Desktop\\YieldVault-RWA\\frontend\\node_modules\\@testing-library\\dom\\dist\\wait-for.js:163:27)\n at C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/TransactionHistory.test.tsx:283:11\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1893:20"],"meta":{},"tags":[]}],"startTime":1774558025986,"endTime":1774558032085.3718,"status":"failed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/TransactionHistory.test.tsx"},{"assertionResults":[{"ancestorTitles":["FormField"],"fullName":"FormField renders label and input","status":"passed","title":"renders label and input","duration":709.0949,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["FormField"],"fullName":"FormField shows error and aria-invalid when error is provided","status":"passed","title":"shows error and aria-invalid when error is provided","duration":30.851399999999558,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["FormField"],"fullName":"FormField does not render an error element when there is no error","status":"passed","title":"does not render an error element when there is no error","duration":16.754399999999805,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558024305,"endTime":1774558025061.7544,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/components/FormField.test.tsx"},{"assertionResults":[{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton shows loading label when loading","status":"passed","title":"shows loading label when loading","duration":769.7527000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton is disabled while loading","status":"passed","title":"is disabled while loading","duration":79.72260000000097,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton is disabled when disabled prop is true","status":"passed","title":"is disabled when disabled prop is true","duration":84.5065999999988,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558024304,"endTime":1774558025238.5066,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/components/SubmitButton.test.tsx"},{"assertionResults":[{"ancestorTitles":["ApiClient"],"fullName":"ApiClient retries transient GET failures with backoff and emits telemetry","status":"passed","title":"retries transient GET failures with backoff and emits telemetry","duration":26.477100000000064,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient does not retry non-idempotent requests","status":"passed","title":"does not retry non-idempotent requests","duration":4.21599999999944,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient normalizes terminal HTTP errors","status":"passed","title":"normalizes terminal HTTP errors","duration":3.8078000000004977,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient sets X-Correlation-ID header on outgoing requests via the request interceptor","status":"passed","title":"sets X-Correlation-ID header on outgoing requests via the request interceptor","duration":2.127900000000409,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient attaches correlationId to ApiError on HTTP failure","status":"passed","title":"attaches correlationId to ApiError on HTTP failure","duration":2.2916000000004715,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558036005,"endTime":1774558036044.2915,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/api/client.test.ts"},{"assertionResults":[{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema accepts a valid G... address (56 chars)","status":"passed","title":"accepts a valid G... address (56 chars)","duration":22.479600000000573,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an empty string","status":"passed","title":"rejects an empty string","duration":3.145999999999731,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an address that starts with a different letter","status":"passed","title":"rejects an address that starts with a different letter","duration":1.043999999999869,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an address that is too short","status":"passed","title":"rejects an address that is too short","duration":1.2951999999995678,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects a non-string input","status":"passed","title":"rejects a non-string input","duration":2.988299999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema trims surrounding whitespace and still validates a correct address","status":"passed","title":"trims surrounding whitespace and still validates a correct address","duration":0.6626999999998588,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema accepts a whole number string","status":"passed","title":"accepts a whole number string","duration":1.2229999999999563,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema accepts a decimal string with up to 7 decimal places","status":"passed","title":"accepts a decimal string with up to 7 decimal places","duration":0.8841000000002168,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects zero","status":"passed","title":"rejects zero","duration":4.054900000000089,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects a negative amount","status":"passed","title":"rejects a negative amount","duration":1.5424000000002707,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects more than 7 decimal places","status":"passed","title":"rejects more than 7 decimal places","duration":1.0471999999999753,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects a non-numeric string","status":"passed","title":"rejects a non-numeric string","duration":0.9118000000007669,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema accepts a valid deposit payload","status":"passed","title":"accepts a valid deposit payload","duration":5.945300000000316,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema accepts optional slippageBps within range","status":"passed","title":"accepts optional slippageBps within range","duration":1.3613000000004831,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":7.312200000000303,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects a missing amount","status":"passed","title":"rejects a missing amount","duration":0.9618000000000393,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects zero amount","status":"passed","title":"rejects zero amount","duration":0.7761000000000422,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects an unsupported asset","status":"passed","title":"rejects an unsupported asset","duration":1.047099999999773,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects slippageBps above 500","status":"passed","title":"rejects slippageBps above 500","duration":1.647399999999834,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects negative slippageBps","status":"passed","title":"rejects negative slippageBps","duration":0.9997000000003027,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema includes field-level detail for the offending field","status":"passed","title":"includes field-level detail for the offending field","duration":1.094000000000051,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema accepts a valid withdrawal payload","status":"passed","title":"accepts a valid withdrawal payload","duration":1.643500000000131,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema accepts an optional destination address","status":"passed","title":"accepts an optional destination address","duration":9.012299999999414,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects zero shares","status":"passed","title":"rejects zero shares","duration":0.8176999999996042,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects fractional shares","status":"passed","title":"rejects fractional shares","duration":0.7831999999998516,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects an invalid destination address","status":"passed","title":"rejects an invalid destination address","duration":0.8258000000005268,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":1.0281999999997424,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema accepts two valid dates where from <= to","status":"passed","title":"accepts two valid dates where from <= to","duration":2.061800000000403,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema accepts an empty object (all fields are optional)","status":"passed","title":"accepts an empty object (all fields are optional)","duration":0.6378000000004249,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects from > to with a 'from' field-level detail","status":"passed","title":"rejects from > to with a 'from' field-level detail","duration":1.3512999999993554,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects a malformed date string","status":"passed","title":"rejects a malformed date string","duration":0.9475999999995111,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects limit > 365","status":"passed","title":"rejects limit > 365","duration":1.0468000000000757,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema accepts a valid address and defaults status to 'all'","status":"passed","title":"accepts a valid address and defaults status to 'all'","duration":2.2115999999996347,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema accepts an explicit status filter","status":"passed","title":"accepts an explicit status filter","duration":1.168100000000777,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema rejects an invalid status value","status":"passed","title":"rejects an invalid status value","duration":0.856300000000374,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":0.7511999999996988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema applies default limit=50, order=desc, type=all","status":"passed","title":"applies default limit=50, order=desc, type=all","duration":1.3775000000005093,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema accepts a custom limit within range","status":"passed","title":"accepts a custom limit within range","duration":0.4686999999994441,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects limit above 200","status":"passed","title":"rejects limit above 200","duration":0.9025000000001455,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects an invalid order value","status":"passed","title":"rejects an invalid order value","duration":0.7731999999996333,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":0.7451000000000931,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() returns the parsed value on success","status":"passed","title":"returns the parsed value on success","duration":0.40310000000044965,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() throws ValidationError (not a generic Error) on failure","status":"passed","title":"throws ValidationError (not a generic Error) on failure","duration":1.0083000000004176,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() includes the context label in the error message when provided","status":"passed","title":"includes the context label in the error message when provided","duration":0.7548999999999069,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() error.toJSON() returns the canonical shape","status":"passed","title":"error.toJSON() returns the canonical shape","duration":5.640500000000429,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateAsync()"],"fullName":"validateAsync() resolves with the parsed value on success","status":"passed","title":"resolves with the parsed value on success","duration":2.344100000000253,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateAsync()"],"fullName":"validateAsync() rejects with ValidationError on failure","status":"passed","title":"rejects with ValidationError on failure","duration":2.032000000000153,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558033350,"endTime":1774558033460.032,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/api/validation.test.ts"}]}
diff --git a/frontend/vitest-results2.json b/frontend/vitest-results2.json
deleted file mode 100644
index f87257b44..000000000
--- a/frontend/vitest-results2.json
+++ /dev/null
@@ -1,2 +0,0 @@
-[sentry-vite-plugin] Info: Sending telemetry data on issues and performance to Sentry. To disable telemetry, set `options.telemetry` to `false`.
-{"numTotalTestSuites":36,"numPassedTestSuites":32,"numFailedTestSuites":4,"numPendingTestSuites":0,"numTotalTests":106,"numPassedTests":102,"numFailedTests":4,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1774558233227,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["Navbar"],"fullName":"Navbar renders the navbar with navigation links","status":"passed","title":"renders the navbar with navigation links","duration":530.8791999999994,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Navbar"],"fullName":"Navbar renders the wallet connect button","status":"passed","title":"renders the wallet connect button","duration":74.41929999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Navbar"],"fullName":"Navbar shows the truncated wallet address when connected","status":"passed","title":"shows the truncated wallet address when connected","duration":81.50749999999971,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558257280,"endTime":1774558257966.5076,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/Navbar.test.tsx"},{"assertionResults":[{"ancestorTitles":["ThemeToggle"],"fullName":"ThemeToggle renders the theme toggle button","status":"passed","title":"renders the theme toggle button","duration":761.6308000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ThemeToggle"],"fullName":"ThemeToggle changes the theme when clicked","status":"passed","title":"changes the theme when clicked","duration":115.44390000000021,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558244375,"endTime":1774558245252.4438,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/ThemeToggle.test.tsx"},{"assertionResults":[{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard renders the connect overlay when wallet is not connected","status":"passed","title":"renders the connect overlay when wallet is not connected","duration":665.4140999999981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard renders the dashboard when wallet is connected","status":"passed","title":"renders the dashboard when wallet is connected","duration":182.77189999999973,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard allows switching between deposit and withdraw tabs","status":"passed","title":"allows switching between deposit and withdraw tabs","duration":265.634399999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard updates the amount input and processes a deposit","status":"failed","title":"updates the amount input and processes a deposit","duration":5028.278999999999,"failureMessages":["Error: STACK_TRACE_ERROR\n at task (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1774:27)\n at Object. (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1807:16)\n at Object. (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1553:28)\n at chain (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:589:14)\n at C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/VaultDashboard.test.tsx:105:3\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1879:40\n at runWithSuite (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2248:8)\n at Object.collect (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1879:10)\n at Object.collect (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1883:54)\n at processTicksAndRejections (node:internal/process/task_queues:105:5)"],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard shows a normalized API error message when data loading fails","status":"passed","title":"shows a normalized API error message when data loading fails","duration":1269.7649999999994,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558251248,"endTime":1774558258661.765,"status":"failed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/VaultDashboard.test.tsx"},{"assertionResults":[{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect renders the connect button when no wallet is connected","status":"passed","title":"renders the connect button when no wallet is connected","duration":226.60679999999957,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect calls onConnect when manually connected via button","status":"passed","title":"calls onConnect when manually connected via button","duration":82.07099999999991,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect shows the formatted address when connected","status":"passed","title":"shows the formatted address when connected","duration":75.0030999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect calls onDisconnect when the disconnect button is clicked","status":"passed","title":"calls onDisconnect when the disconnect button is clicked","duration":47.60159999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect handles wallet disconnects gracefully during polling","status":"passed","title":"handles wallet disconnects gracefully during polling","duration":33.1786000000011,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558257281,"endTime":1774558257746.1787,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/WalletConnect.test.tsx"},{"assertionResults":[{"ancestorTitles":["ToastProvider"],"fullName":"ToastProvider shows and auto-dismisses toasts","status":"passed","title":"shows and auto-dismisses toasts","duration":870.4106000000011,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ToastProvider"],"fullName":"ToastProvider supports manual dismissal","status":"passed","title":"supports manual dismissal","duration":237.5660000000007,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558243293,"endTime":1774558244400.566,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/context/ToastContext.test.tsx"},{"assertionResults":[{"ancestorTitles":["useForm"],"fullName":"useForm returns initial state","status":"passed","title":"returns initial state","duration":62.9393,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleChange updates values","status":"passed","title":"handleChange updates values","duration":11.664999999999054,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleBlur marks touched and validates field","status":"passed","title":"handleBlur marks touched and validates field","duration":16.35820000000058,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleSubmit validates all fields and blocks submit when invalid","status":"passed","title":"handleSubmit validates all fields and blocks submit when invalid","duration":14.084100000000035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm tracks isSubmitting lifecycle for valid submit","status":"passed","title":"tracks isSubmitting lifecycle for valid submit","duration":33.50980000000163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm setFieldError stores server-side error","status":"passed","title":"setFieldError stores server-side error","duration":4.847400000000562,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558254886,"endTime":1774558255029.8474,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/useForm.test.tsx"},{"assertionResults":[{"ancestorTitles":["validate"],"fullName":"validate fails when required field is missing","status":"passed","title":"fails when required field is missing","duration":4.731899999999769,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails min length and passes when min is met","status":"passed","title":"fails min length and passes when min is met","duration":1.2265999999999622,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails max length and passes when within max","status":"passed","title":"fails max length and passes when within max","duration":0.8139000000001033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails pattern and passes matching pattern","status":"passed","title":"fails pattern and passes matching pattern","duration":0.7382999999999811,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate runs custom validator","status":"passed","title":"runs custom validator","duration":0.7346000000006825,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558262239,"endTime":1774558262248.7346,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/validate.test.ts"},{"assertionResults":[{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount returns null when Freighter is not allowed","status":"passed","title":"returns null when Freighter is not allowed","duration":10.690499999999702,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount returns discovered address when Freighter is allowed","status":"passed","title":"returns discovered address when Freighter is allowed","duration":2.222099999999955,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount extracts USDC balance from account balances","status":"passed","title":"extracts USDC balance from account balances","duration":3.4658999999992375,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558259507,"endTime":1774558259523.4658,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/stellarAccount.test.ts"},{"assertionResults":[{"ancestorTitles":["Portfolio"],"fullName":"Portfolio shows the wallet prompt when disconnected","status":"passed","title":"shows the wallet prompt when disconnected","duration":323.08020000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio renders holdings in the reusable table","status":"failed","title":"renders holdings in the reusable table","duration":1422.4457000000002,"failureMessages":["Error: Unable to find an element with the text: /Tokenized T-Bills/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mData unavailable\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mInvalid value for \"walletAddress\": Must be a valid Stellar public key (starts with G, 56 chars).\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTotal Assets\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mUnrealized Gain\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m+\u001b[0m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mHoldings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort, search, and page through all current vault positions.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStatus Filter\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll Statuses\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mActive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInactive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSearch holdings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 holdings found\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
)\n at runWithCancel (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2313:10)\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2295:20\n at new Promise ()"],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio persists filter state in the URL","status":"failed","title":"persists filter state in the URL","duration":1261.7958999999992,"failureMessages":["Error: Unable to find an element with the text: /Tokenized T-Bills/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mData unavailable\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mInvalid value for \"walletAddress\": Must be a valid Stellar public key (starts with G, 56 chars).\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTotal Assets\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mUnrealized Gain\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m+\u001b[0m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mHoldings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort, search, and page through all current vault positions.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStatus Filter\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll Statuses\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mActive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInactive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSearch holdings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mReset Filters\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 holdings found\u001b[...\n at waitForWrapper (C:\\Users\\HP\\Desktop\\YieldVault-RWA\\frontend\\node_modules\\@testing-library\\dom\\dist\\wait-for.js:163:27)\n at C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/Portfolio.test.tsx:151:11\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:1893:20"],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio supports keyboard sorting and pagination state from the URL","status":"failed","title":"supports keyboard sorting and pagination state from the URL","duration":1215.0582000000013,"failureMessages":["Error: Unable to find an element with the text: /Yield Bearing Cash/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mData unavailable\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mInvalid value for \"walletAddress\": Must be a valid Stellar public key (starts with G, 56 chars).\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mTotal Assets\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mUnrealized Gain\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0m+\u001b[0m\n \u001b[0m$0.00\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mHoldings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSort, search, and page through all current vault positions.\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mStatus Filter\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mAll Statuses\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mActive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mInactive\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mSearch holdings\u001b[0m\n \u001b[36m \u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m \u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m0 holdings found\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m
)\n at runWithCancel (file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2313:10)\n at file:///C:/Users/HP/Desktop/YieldVault-RWA/frontend/node_modules/@vitest/runner/dist/chunk-artifact.js:2295:20\n at new Promise ()"],"meta":{},"tags":[]}],"startTime":1774558245572,"endTime":1774558249794.058,"status":"failed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/Portfolio.test.tsx"},{"assertionResults":[{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders connect-wallet prompt when walletAddress is null","status":"passed","title":"renders connect-wallet prompt when walletAddress is null","duration":210.30199999999968,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows loading indicator while fetch is pending","status":"passed","title":"shows loading indicator while fetch is pending","duration":186.41710000000057,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory calls getTransactions with the correct wallet address on mount","status":"passed","title":"calls getTransactions with the correct wallet address on mount","duration":120.14270000000033,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders the transaction table when wallet is connected and fetch succeeds","status":"passed","title":"renders the transaction table when wallet is connected and fetch succeeds","duration":895.4228000000003,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows ApiStatusBanner on fetch failure","status":"passed","title":"shows ApiStatusBanner on fetch failure","duration":427.71639999999934,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders correct column headers: Type, Amount, Asset, Date, Transaction Hash","status":"passed","title":"renders correct column headers: Type, Amount, Asset, Date, Transaction Hash","duration":828.6372999999985,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory has sort buttons for Type, Amount, Date but not for Asset and Transaction Hash","status":"passed","title":"has sort buttons for Type, Amount, Date but not for Asset and Transaction Hash","duration":449.1260000000002,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory default page size select shows 10","status":"passed","title":"default page size select shows 10","duration":323.40560000000005,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders filter control with All, Deposit, and Withdrawal options","status":"passed","title":"renders filter control with All, Deposit, and Withdrawal options","duration":243.00400000000081,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory resets page to 1 when filter is applied","status":"passed","title":"resets page to 1 when filter is applied","duration":925.0737000000008,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders deposit badge with 'cyan' class and withdrawal badge with 'red' class","status":"passed","title":"renders deposit badge with 'cyan' class and withdrawal badge with 'red' class","duration":465.06540000000314,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows empty state message when wallet is connected but no transactions exist","status":"passed","title":"shows empty state message when wallet is connected but no transactions exist","duration":207.54369999999835,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows filtered empty state message when filter yields no results","status":"passed","title":"shows filtered empty state message when filter yields no results","duration":594.2430000000022,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558245266,"endTime":1774558251143.243,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/TransactionHistory.test.tsx"},{"assertionResults":[{"ancestorTitles":["FormField"],"fullName":"FormField renders label and input","status":"passed","title":"renders label and input","duration":893.1222000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["FormField"],"fullName":"FormField shows error and aria-invalid when error is provided","status":"passed","title":"shows error and aria-invalid when error is provided","duration":59.21819999999934,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["FormField"],"fullName":"FormField does not render an error element when there is no error","status":"passed","title":"does not render an error element when there is no error","duration":22.307200000001103,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558243651,"endTime":1774558244626.3071,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/components/FormField.test.tsx"},{"assertionResults":[{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton shows loading label when loading","status":"passed","title":"shows loading label when loading","duration":812.3600000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton is disabled while loading","status":"passed","title":"is disabled while loading","duration":48.02190000000155,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton is disabled when disabled prop is true","status":"passed","title":"is disabled when disabled prop is true","duration":75.21390000000065,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558243652,"endTime":1774558244588.2139,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/components/SubmitButton.test.tsx"},{"assertionResults":[{"ancestorTitles":["ApiClient"],"fullName":"ApiClient retries transient GET failures with backoff and emits telemetry","status":"passed","title":"retries transient GET failures with backoff and emits telemetry","duration":51.91020000000026,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient does not retry non-idempotent requests","status":"passed","title":"does not retry non-idempotent requests","duration":7.0341000000007625,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient normalizes terminal HTTP errors","status":"passed","title":"normalizes terminal HTTP errors","duration":9.873700000000099,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient sets X-Correlation-ID header on outgoing requests via the request interceptor","status":"passed","title":"sets X-Correlation-ID header on outgoing requests via the request interceptor","duration":3.7035999999989144,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient attaches correlationId to ApiError on HTTP failure","status":"passed","title":"attaches correlationId to ApiError on HTTP failure","duration":4.145500000000538,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558259604,"endTime":1774558259682.1455,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/api/client.test.ts"},{"assertionResults":[{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema accepts a valid G... address (56 chars)","status":"passed","title":"accepts a valid G... address (56 chars)","duration":8.347499999999854,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an empty string","status":"passed","title":"rejects an empty string","duration":4.1314000000002125,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an address that starts with a different letter","status":"passed","title":"rejects an address that starts with a different letter","duration":1.023300000000745,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an address that is too short","status":"passed","title":"rejects an address that is too short","duration":1.3470999999990454,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects a non-string input","status":"passed","title":"rejects a non-string input","duration":3.3536999999996624,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema trims surrounding whitespace and still validates a correct address","status":"passed","title":"trims surrounding whitespace and still validates a correct address","duration":1.3683000000000902,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema accepts a whole number string","status":"passed","title":"accepts a whole number string","duration":3.042199999999866,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema accepts a decimal string with up to 7 decimal places","status":"passed","title":"accepts a decimal string with up to 7 decimal places","duration":2.7672999999995227,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects zero","status":"passed","title":"rejects zero","duration":2.5247999999992317,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects a negative amount","status":"passed","title":"rejects a negative amount","duration":1.3438000000005559,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects more than 7 decimal places","status":"passed","title":"rejects more than 7 decimal places","duration":1.0992000000005646,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects a non-numeric string","status":"passed","title":"rejects a non-numeric string","duration":0.9547000000002299,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema accepts a valid deposit payload","status":"passed","title":"accepts a valid deposit payload","duration":5.219699999999648,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema accepts optional slippageBps within range","status":"passed","title":"accepts optional slippageBps within range","duration":2.38379999999961,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":1.8989000000001397,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects a missing amount","status":"passed","title":"rejects a missing amount","duration":6.348700000000463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects zero amount","status":"passed","title":"rejects zero amount","duration":3.4570000000003347,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects an unsupported asset","status":"passed","title":"rejects an unsupported asset","duration":1.0830999999998312,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects slippageBps above 500","status":"passed","title":"rejects slippageBps above 500","duration":1.2101000000002387,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects negative slippageBps","status":"passed","title":"rejects negative slippageBps","duration":0.8960000000006403,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema includes field-level detail for the offending field","status":"passed","title":"includes field-level detail for the offending field","duration":2.1238000000012107,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema accepts a valid withdrawal payload","status":"passed","title":"accepts a valid withdrawal payload","duration":2.279000000000451,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema accepts an optional destination address","status":"passed","title":"accepts an optional destination address","duration":0.8493999999991502,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects zero shares","status":"passed","title":"rejects zero shares","duration":0.8171000000002095,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects fractional shares","status":"passed","title":"rejects fractional shares","duration":0.8546999999998661,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects an invalid destination address","status":"passed","title":"rejects an invalid destination address","duration":1.100800000000163,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":1.1419000000005326,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema accepts two valid dates where from <= to","status":"passed","title":"accepts two valid dates where from <= to","duration":2.1005000000004657,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema accepts an empty object (all fields are optional)","status":"passed","title":"accepts an empty object (all fields are optional)","duration":0.6725999999998749,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects from > to with a 'from' field-level detail","status":"passed","title":"rejects from > to with a 'from' field-level detail","duration":0.7616999999991094,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects a malformed date string","status":"passed","title":"rejects a malformed date string","duration":0.8220000000001164,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects limit > 365","status":"passed","title":"rejects limit > 365","duration":1.1183000000000902,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema accepts a valid address and defaults status to 'all'","status":"passed","title":"accepts a valid address and defaults status to 'all'","duration":2.9641000000010536,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema accepts an explicit status filter","status":"passed","title":"accepts an explicit status filter","duration":1.1793999999990774,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema rejects an invalid status value","status":"passed","title":"rejects an invalid status value","duration":1.0388999999995576,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":0.8744999999998981,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema applies default limit=50, order=desc, type=all","status":"passed","title":"applies default limit=50, order=desc, type=all","duration":1.499099999999089,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema accepts a custom limit within range","status":"passed","title":"accepts a custom limit within range","duration":0.5073999999985972,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects limit above 200","status":"passed","title":"rejects limit above 200","duration":1.271999999999025,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects an invalid order value","status":"passed","title":"rejects an invalid order value","duration":1.5300000000006548,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":1.4095000000015716,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() returns the parsed value on success","status":"passed","title":"returns the parsed value on success","duration":0.42189999999936845,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() throws ValidationError (not a generic Error) on failure","status":"passed","title":"throws ValidationError (not a generic Error) on failure","duration":1.2181000000000495,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() includes the context label in the error message when provided","status":"passed","title":"includes the context label in the error message when provided","duration":0.9503999999997177,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() error.toJSON() returns the canonical shape","status":"passed","title":"error.toJSON() returns the canonical shape","duration":7.324800000000323,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateAsync()"],"fullName":"validateAsync() resolves with the parsed value on success","status":"passed","title":"resolves with the parsed value on success","duration":2.910700000000361,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateAsync()"],"fullName":"validateAsync() rejects with ValidationError on failure","status":"passed","title":"rejects with ValidationError on failure","duration":2.4068999999999505,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774558256081,"endTime":1774558256182.407,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/api/validation.test.ts"}]}
diff --git a/frontend/vitest-results3.json b/frontend/vitest-results3.json
deleted file mode 100644
index f6502870d..000000000
--- a/frontend/vitest-results3.json
+++ /dev/null
@@ -1,2 +0,0 @@
-[sentry-vite-plugin] Info: Sending telemetry data on issues and performance to Sentry. To disable telemetry, set `options.telemetry` to `false`.
-{"numTotalTestSuites":36,"numPassedTestSuites":36,"numFailedTestSuites":0,"numPendingTestSuites":0,"numTotalTests":106,"numPassedTests":106,"numFailedTests":0,"numPendingTests":0,"numTodoTests":0,"snapshot":{"added":0,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0,"didUpdate":false},"startTime":1774560169295,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["Navbar"],"fullName":"Navbar renders the navbar with navigation links","status":"passed","title":"renders the navbar with navigation links","duration":416.7017999999989,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Navbar"],"fullName":"Navbar renders the wallet connect button","status":"passed","title":"renders the wallet connect button","duration":134.65250000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Navbar"],"fullName":"Navbar shows the truncated wallet address when connected","status":"passed","title":"shows the truncated wallet address when connected","duration":123.09419999999955,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560195490,"endTime":1774560196165.0942,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/Navbar.test.tsx"},{"assertionResults":[{"ancestorTitles":["ThemeToggle"],"fullName":"ThemeToggle renders the theme toggle button","status":"passed","title":"renders the theme toggle button","duration":703.1424000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ThemeToggle"],"fullName":"ThemeToggle changes the theme when clicked","status":"passed","title":"changes the theme when clicked","duration":97.84799999999996,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560183475,"endTime":1774560184276.848,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/ThemeToggle.test.tsx"},{"assertionResults":[{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard renders the connect overlay when wallet is not connected","status":"passed","title":"renders the connect overlay when wallet is not connected","duration":734.5998,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard renders the dashboard when wallet is connected","status":"passed","title":"renders the dashboard when wallet is connected","duration":453.7635999999984,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard allows switching between deposit and withdraw tabs","status":"passed","title":"allows switching between deposit and withdraw tabs","duration":470.8028999999988,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard updates the amount input and processes a deposit","status":"passed","title":"updates the amount input and processes a deposit","duration":520.9108000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultDashboard"],"fullName":"VaultDashboard shows a normalized API error message when data loading fails","status":"passed","title":"shows a normalized API error message when data loading fails","duration":1847.174500000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560187964,"endTime":1774560191992.1746,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/VaultDashboard.test.tsx"},{"assertionResults":[{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect renders the connect button when no wallet is connected","status":"passed","title":"renders the connect button when no wallet is connected","duration":237.03759999999966,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect calls onConnect when manually connected via button","status":"passed","title":"calls onConnect when manually connected via button","duration":104.52359999999862,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect shows the formatted address when connected","status":"passed","title":"shows the formatted address when connected","duration":92.3044000000009,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect calls onDisconnect when the disconnect button is clicked","status":"passed","title":"calls onDisconnect when the disconnect button is clicked","duration":60.349199999998746,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WalletConnect"],"fullName":"WalletConnect handles wallet disconnects gracefully during polling","status":"passed","title":"handles wallet disconnects gracefully during polling","duration":32.34449999999924,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560195492,"endTime":1774560196019.3445,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/components/WalletConnect.test.tsx"},{"assertionResults":[{"ancestorTitles":["ToastProvider"],"fullName":"ToastProvider shows and auto-dismisses toasts","status":"passed","title":"shows and auto-dismisses toasts","duration":709.0792000000001,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ToastProvider"],"fullName":"ToastProvider supports manual dismissal","status":"passed","title":"supports manual dismissal","duration":147.77070000000094,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560182768,"endTime":1774560183625.7708,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/context/ToastContext.test.tsx"},{"assertionResults":[{"ancestorTitles":["useForm"],"fullName":"useForm returns initial state","status":"passed","title":"returns initial state","duration":78.48390000000109,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleChange updates values","status":"passed","title":"handleChange updates values","duration":11.630800000000818,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleBlur marks touched and validates field","status":"passed","title":"handleBlur marks touched and validates field","duration":14.70509999999922,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm handleSubmit validates all fields and blocks submit when invalid","status":"passed","title":"handleSubmit validates all fields and blocks submit when invalid","duration":12.184199999999691,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm tracks isSubmitting lifecycle for valid submit","status":"passed","title":"tracks isSubmitting lifecycle for valid submit","duration":16.47910000000047,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["useForm"],"fullName":"useForm setFieldError stores server-side error","status":"passed","title":"setFieldError stores server-side error","duration":6.287900000001173,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560193089,"endTime":1774560193230.2878,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/useForm.test.tsx"},{"assertionResults":[{"ancestorTitles":["validate"],"fullName":"validate fails when required field is missing","status":"passed","title":"fails when required field is missing","duration":5.153500000000349,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails min length and passes when min is met","status":"passed","title":"fails min length and passes when min is met","duration":1.098800000000665,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails max length and passes when within max","status":"passed","title":"fails max length and passes when within max","duration":0.7452000000002954,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate fails pattern and passes matching pattern","status":"passed","title":"fails pattern and passes matching pattern","duration":0.5659999999998035,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate"],"fullName":"validate runs custom validator","status":"passed","title":"runs custom validator","duration":0.5159999999996217,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560197865,"endTime":1774560197873.516,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/validate.test.ts"},{"assertionResults":[{"ancestorTitles":["Portfolio"],"fullName":"Portfolio shows the wallet prompt when disconnected","status":"passed","title":"shows the wallet prompt when disconnected","duration":231.69859999999971,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio renders holdings in the reusable table","status":"passed","title":"renders holdings in the reusable table","duration":1311.7322999999997,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio persists filter state in the URL","status":"passed","title":"persists filter state in the URL","duration":275.2228000000032,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["Portfolio"],"fullName":"Portfolio supports keyboard sorting and pagination state from the URL","status":"passed","title":"supports keyboard sorting and pagination state from the URL","duration":479.47739999999976,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560184999,"endTime":1774560187310.4773,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/Portfolio.test.tsx"},{"assertionResults":[{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders connect-wallet prompt when walletAddress is null","status":"passed","title":"renders connect-wallet prompt when walletAddress is null","duration":299.3245000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows loading indicator while fetch is pending","status":"passed","title":"shows loading indicator while fetch is pending","duration":203.8119000000006,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory calls getTransactions with the correct wallet address on mount","status":"passed","title":"calls getTransactions with the correct wallet address on mount","duration":89.91960000000108,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders the transaction table when wallet is connected and fetch succeeds","status":"passed","title":"renders the transaction table when wallet is connected and fetch succeeds","duration":775.1518000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows ApiStatusBanner on fetch failure","status":"passed","title":"shows ApiStatusBanner on fetch failure","duration":272.08910000000105,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders correct column headers: Type, Amount, Asset, Date, Transaction Hash","status":"passed","title":"renders correct column headers: Type, Amount, Asset, Date, Transaction Hash","duration":530.4857999999986,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory has sort buttons for Type, Amount, Date but not for Asset and Transaction Hash","status":"passed","title":"has sort buttons for Type, Amount, Date but not for Asset and Transaction Hash","duration":386.3182000000015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory default page size select shows 10","status":"passed","title":"default page size select shows 10","duration":183.38530000000173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders filter control with All, Deposit, and Withdrawal options","status":"passed","title":"renders filter control with All, Deposit, and Withdrawal options","duration":166.3578000000016,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory resets page to 1 when filter is applied","status":"passed","title":"resets page to 1 when filter is applied","duration":691.2407999999996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory renders deposit badge with 'cyan' class and withdrawal badge with 'red' class","status":"passed","title":"renders deposit badge with 'cyan' class and withdrawal badge with 'red' class","duration":314.5601999999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows empty state message when wallet is connected but no transactions exist","status":"passed","title":"shows empty state message when wallet is connected but no transactions exist","duration":103.36520000000019,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionHistory"],"fullName":"TransactionHistory shows filtered empty state message when filter yields no results","status":"passed","title":"shows filtered empty state message when filter yields no results","duration":609.067500000001,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560184670,"endTime":1774560189296.0674,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/pages/TransactionHistory.test.tsx"},{"assertionResults":[{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount returns null when Freighter is not allowed","status":"passed","title":"returns null when Freighter is not allowed","duration":7.574800000000323,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount returns discovered address when Freighter is allowed","status":"passed","title":"returns discovered address when Freighter is allowed","duration":1.1943999999994048,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["stellarAccount"],"fullName":"stellarAccount extracts USDC balance from account balances","status":"passed","title":"extracts USDC balance from account balances","duration":2.8676000000004933,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560199782,"endTime":1774560199793.8677,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/stellarAccount.test.ts"},{"assertionResults":[{"ancestorTitles":["FormField"],"fullName":"FormField renders label and input","status":"passed","title":"renders label and input","duration":751.6338000000014,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["FormField"],"fullName":"FormField shows error and aria-invalid when error is provided","status":"passed","title":"shows error and aria-invalid when error is provided","duration":38.95440000000053,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["FormField"],"fullName":"FormField does not render an error element when there is no error","status":"passed","title":"does not render an error element when there is no error","duration":11.274600000000646,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560182857,"endTime":1774560183659.2747,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/components/FormField.test.tsx"},{"assertionResults":[{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton shows loading label when loading","status":"passed","title":"shows loading label when loading","duration":558.775599999999,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton is disabled while loading","status":"passed","title":"is disabled while loading","duration":56.511499999998705,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["SubmitButton"],"fullName":"SubmitButton is disabled when disabled prop is true","status":"passed","title":"is disabled when disabled prop is true","duration":55.920099999999366,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560182740,"endTime":1774560183410.9202,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/forms/components/SubmitButton.test.tsx"},{"assertionResults":[{"ancestorTitles":["ApiClient"],"fullName":"ApiClient retries transient GET failures with backoff and emits telemetry","status":"passed","title":"retries transient GET failures with backoff and emits telemetry","duration":43.126899999999296,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient does not retry non-idempotent requests","status":"passed","title":"does not retry non-idempotent requests","duration":6.873099999998885,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient normalizes terminal HTTP errors","status":"passed","title":"normalizes terminal HTTP errors","duration":6.994899999999689,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient sets X-Correlation-ID header on outgoing requests via the request interceptor","status":"passed","title":"sets X-Correlation-ID header on outgoing requests via the request interceptor","duration":2.737300000000687,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["ApiClient"],"fullName":"ApiClient attaches correlationId to ApiError on HTTP failure","status":"passed","title":"attaches correlationId to ApiError on HTTP failure","duration":2.561999999999898,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560197076,"endTime":1774560197138.562,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/api/client.test.ts"},{"assertionResults":[{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema accepts a valid G... address (56 chars)","status":"passed","title":"accepts a valid G... address (56 chars)","duration":10.230999999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an empty string","status":"passed","title":"rejects an empty string","duration":3.1103000000002794,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an address that starts with a different letter","status":"passed","title":"rejects an address that starts with a different letter","duration":1.08009999999922,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects an address that is too short","status":"passed","title":"rejects an address that is too short","duration":1.5487999999986641,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema rejects a non-string input","status":"passed","title":"rejects a non-string input","duration":1.7580999999991036,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["StellarAddressSchema"],"fullName":"StellarAddressSchema trims surrounding whitespace and still validates a correct address","status":"passed","title":"trims surrounding whitespace and still validates a correct address","duration":0.9565000000002328,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema accepts a whole number string","status":"passed","title":"accepts a whole number string","duration":1.6815999999998894,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema accepts a decimal string with up to 7 decimal places","status":"passed","title":"accepts a decimal string with up to 7 decimal places","duration":1.5480999999999767,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects zero","status":"passed","title":"rejects zero","duration":3.3407999999999447,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects a negative amount","status":"passed","title":"rejects a negative amount","duration":1.5767999999989115,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects more than 7 decimal places","status":"passed","title":"rejects more than 7 decimal places","duration":1.43789999999899,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["AmountSchema"],"fullName":"AmountSchema rejects a non-numeric string","status":"passed","title":"rejects a non-numeric string","duration":1.156699999999546,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema accepts a valid deposit payload","status":"passed","title":"accepts a valid deposit payload","duration":6.332000000000335,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema accepts optional slippageBps within range","status":"passed","title":"accepts optional slippageBps within range","duration":2.3999000000003434,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":1.67909999999938,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects a missing amount","status":"passed","title":"rejects a missing amount","duration":1.0658999999996013,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects zero amount","status":"passed","title":"rejects zero amount","duration":1.1547999999984313,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects an unsupported asset","status":"passed","title":"rejects an unsupported asset","duration":1.345500000001266,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects slippageBps above 500","status":"passed","title":"rejects slippageBps above 500","duration":1.5191000000013446,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema rejects negative slippageBps","status":"passed","title":"rejects negative slippageBps","duration":1.020200000000841,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["DepositRequestSchema"],"fullName":"DepositRequestSchema includes field-level detail for the offending field","status":"passed","title":"includes field-level detail for the offending field","duration":1.3634999999994761,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema accepts a valid withdrawal payload","status":"passed","title":"accepts a valid withdrawal payload","duration":2.0733000000000175,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema accepts an optional destination address","status":"passed","title":"accepts an optional destination address","duration":0.9553999999989173,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects zero shares","status":"passed","title":"rejects zero shares","duration":1.2782999999999447,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects fractional shares","status":"passed","title":"rejects fractional shares","duration":1.5445999999992637,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects an invalid destination address","status":"passed","title":"rejects an invalid destination address","duration":1.5267000000003463,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["WithdrawalRequestSchema"],"fullName":"WithdrawalRequestSchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":1.383399999998801,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema accepts two valid dates where from <= to","status":"passed","title":"accepts two valid dates where from <= to","duration":2.134299999999712,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema accepts an empty object (all fields are optional)","status":"passed","title":"accepts an empty object (all fields are optional)","duration":0.7554999999993015,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects from > to with a 'from' field-level detail","status":"passed","title":"rejects from > to with a 'from' field-level detail","duration":0.966099999999642,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects a malformed date string","status":"passed","title":"rejects a malformed date string","duration":0.9799000000002707,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["VaultHistoryQuerySchema"],"fullName":"VaultHistoryQuerySchema rejects limit > 365","status":"passed","title":"rejects limit > 365","duration":1.0925000000006548,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema accepts a valid address and defaults status to 'all'","status":"passed","title":"accepts a valid address and defaults status to 'all'","duration":1.9169000000001688,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema accepts an explicit status filter","status":"passed","title":"accepts an explicit status filter","duration":0.7645999999986088,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema rejects an invalid status value","status":"passed","title":"rejects an invalid status value","duration":0.8930999999993219,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["PortfolioQuerySchema"],"fullName":"PortfolioQuerySchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":0.7651999999998225,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema applies default limit=50, order=desc, type=all","status":"passed","title":"applies default limit=50, order=desc, type=all","duration":1.8410000000003492,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema accepts a custom limit within range","status":"passed","title":"accepts a custom limit within range","duration":1.0049999999991996,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects limit above 200","status":"passed","title":"rejects limit above 200","duration":1.2714999999989232,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects an invalid order value","status":"passed","title":"rejects an invalid order value","duration":0.9574000000011438,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["TransactionQuerySchema"],"fullName":"TransactionQuerySchema rejects a missing walletAddress","status":"passed","title":"rejects a missing walletAddress","duration":0.9200999999993655,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() returns the parsed value on success","status":"passed","title":"returns the parsed value on success","duration":0.41159999999945285,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() throws ValidationError (not a generic Error) on failure","status":"passed","title":"throws ValidationError (not a generic Error) on failure","duration":0.9951999999993859,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() includes the context label in the error message when provided","status":"passed","title":"includes the context label in the error message when provided","duration":0.7281000000002678,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validate()"],"fullName":"validate() error.toJSON() returns the canonical shape","status":"passed","title":"error.toJSON() returns the canonical shape","duration":5.863900000000285,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateAsync()"],"fullName":"validateAsync() resolves with the parsed value on success","status":"passed","title":"resolves with the parsed value on success","duration":2.7247999999999593,"failureMessages":[],"meta":{},"tags":[]},{"ancestorTitles":["validateAsync()"],"fullName":"validateAsync() rejects with ValidationError on failure","status":"passed","title":"rejects with ValidationError on failure","duration":2.9945999999999913,"failureMessages":[],"meta":{},"tags":[]}],"startTime":1774560193789,"endTime":1774560193879.9946,"status":"passed","message":"","name":"C:/Users/HP/Desktop/YieldVault-RWA/frontend/src/lib/api/validation.test.ts"}]}
diff --git a/package.json b/package.json
index 23e8ff2bc..3816e8b19 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,6 @@
"scripts": {
"check:pnpm-version": "node scripts/check-pnpm-version.js",
"prepare": "husky",
- "test": "vitest run",
"lint:staged": "lint-staged",
"commitlint": "commitlint",
"build:schemas": "npm --prefix packages/api-schemas run build",