diff --git a/src/__tests__/ClaimHistoryTable.test.tsx b/src/__tests__/ClaimHistoryTable.test.tsx
index 86342f5..dbc1fe6 100644
--- a/src/__tests__/ClaimHistoryTable.test.tsx
+++ b/src/__tests__/ClaimHistoryTable.test.tsx
@@ -35,6 +35,15 @@ describe('ClaimHistoryTable', () => {
expect(html).toContain('Processing');
});
+ it('links each claim row and mobile card to its policy details', () => {
+ const html = renderToStaticMarkup(
+ ,
+ );
+
+ expect(html.match(/href="\/policies\/policy-123"/g)).toHaveLength(4);
+ expect(html).toContain('aria-label="View policy for claim claim-1"');
+ });
+
it('displays trigger status correctly', () => {
const claims = [
makeClaim({ id: 'claim-1', triggerMet: true }),
diff --git a/src/__tests__/OracleDataWidget.test.tsx b/src/__tests__/OracleDataWidget.test.tsx
index 0a51efd..4ce263c 100644
--- a/src/__tests__/OracleDataWidget.test.tsx
+++ b/src/__tests__/OracleDataWidget.test.tsx
@@ -1,5 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderToStaticMarkup } from 'react-dom/server';
+import { render } from '@testing-library/react';
import { OracleDataWidget } from '../components/OracleDataWidget';
vi.mock('@/hooks/useOracle', () => ({
@@ -46,6 +47,26 @@ describe('OracleDataWidget', () => {
vi.clearAllMocks();
});
+ it('does not rerender when its parent rerenders with unchanged props', () => {
+ mockUseOracleReading.mockReturnValue({
+ reading: null,
+ loading: false,
+ error: null,
+ refetch: vi.fn(),
+ });
+
+ const { rerender } = render(
+ ,
+ );
+ expect(mockUseOracleReading).toHaveBeenCalledTimes(1);
+
+ rerender();
+ expect(mockUseOracleReading).toHaveBeenCalledTimes(1);
+
+ rerender();
+ expect(mockUseOracleReading).toHaveBeenCalledTimes(2);
+ });
+
it('shows skeleton when loading', () => {
mockUseOracleReading.mockReturnValue({
reading: null,
diff --git a/src/__tests__/storage.test.ts b/src/__tests__/storage.test.ts
index 181dd6f..4425171 100644
--- a/src/__tests__/storage.test.ts
+++ b/src/__tests__/storage.test.ts
@@ -12,11 +12,19 @@ const localStorageMock = (() => {
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
+const sessionStorageMock = {
+ getItem: vi.fn<(key: string) => string | null>(() => null),
+ setItem: vi.fn<(key: string, value: string) => void>(),
+ removeItem: vi.fn<(key: string) => void>(),
+};
+
+Object.defineProperty(window, 'sessionStorage', { value: sessionStorageMock });
+
describe('storage', () => {
beforeEach(() => localStorageMock.clear());
it('sets and gets string values', () => {
- storage.set('key', 'value');
+ expect(storage.set('key', 'value')).toBe(true);
expect(storage.get('key')).toBe('value');
});
@@ -40,4 +48,24 @@ describe('storage', () => {
localStorageMock.setItem('bad', 'not-valid-json{');
expect(storage.getJSON('bad')).toBeNull();
});
+
+ it('returns false instead of throwing when local storage quota is exceeded', () => {
+ const setItem = vi.spyOn(localStorage, 'setItem').mockImplementation(() => {
+ throw new DOMException('Storage quota exceeded', 'QuotaExceededError');
+ });
+
+ expect(storage.set('key', 'value')).toBe(false);
+ expect(() => storage.set('key', 'value')).not.toThrow();
+ setItem.mockRestore();
+ });
+
+ it('returns false instead of throwing when session storage quota is exceeded', () => {
+ sessionStorageMock.setItem.mockImplementation(() => {
+ throw new DOMException('Storage quota exceeded', 'QuotaExceededError');
+ });
+
+ expect(storage.setSession('token', 'value')).toBe(false);
+ expect(() => storage.setSession('token', 'value')).not.toThrow();
+ sessionStorageMock.setItem.mockReset();
+ });
});
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 1d7a627..bbed3c1 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -182,7 +182,13 @@ export default function DashboardPage() {
className="border-b border-white/5 transition-colors hover:bg-white/[0.02]"
>
- {claim.id.slice(0, 8)}…
+
+ {claim.id.slice(0, 8)}…
+
|
{formatDateTime(claim.submittedAt)}
diff --git a/src/components/ClaimHistoryTable.tsx b/src/components/ClaimHistoryTable.tsx
index cf8274e..9505574 100644
--- a/src/components/ClaimHistoryTable.tsx
+++ b/src/components/ClaimHistoryTable.tsx
@@ -163,15 +163,21 @@ function ClaimHistoryTableComponent({ claims, className }: ClaimHistoryTableProp
{sortedClaims.map((claim) => (
|
|
- {claim.id.slice(0, 8)}…
+
+ {claim.id.slice(0, 8)}…
+
|
{claim.policyId.slice(0, 8)}…
@@ -189,7 +195,7 @@ function ClaimHistoryTableComponent({ claims, className }: ClaimHistoryTableProp
|
{claim.txHash ? (
-
+
) : (
—
)}
@@ -208,19 +214,23 @@ function ClaimHistoryTableComponent({ claims, className }: ClaimHistoryTableProp
{sortedClaims.map((claim) => (
-
+
{claim.id.slice(0, 8)}…
-
+
Policy
{claim.policyId.slice(0, 8)}…
@@ -237,7 +247,7 @@ function ClaimHistoryTableComponent({ claims, className }: ClaimHistoryTableProp
Tx
{claim.txHash ? (
-
+
) : (
—
)}
diff --git a/src/components/OracleDataWidget.tsx b/src/components/OracleDataWidget.tsx
index 6d0c1e9..70d0ac1 100644
--- a/src/components/OracleDataWidget.tsx
+++ b/src/components/OracleDataWidget.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useState, useCallback } from 'react';
+import { memo, useState, useCallback } from 'react';
import { useOracleReading } from '@/hooks/useOracle';
import { Skeleton } from './Skeleton';
import { formatOracleValue, formatDateTime, formatUtcDateTime } from '@/lib/format';
@@ -11,7 +11,7 @@ interface OracleDataWidgetProps {
className?: string;
}
-export function OracleDataWidget({ oracleKey, className }: OracleDataWidgetProps) {
+function OracleDataWidgetComponent({ oracleKey, className }: OracleDataWidgetProps) {
const { reading, loading, error, refetch } = useOracleReading(oracleKey);
const [refreshing, setRefreshing] = useState(false);
@@ -94,3 +94,10 @@ export function OracleDataWidget({ oracleKey, className }: OracleDataWidgetProps
);
}
+
+export const OracleDataWidget = memo(
+ OracleDataWidgetComponent,
+ (previous, next) => previous.oracleKey === next.oracleKey && previous.className === next.className,
+);
+
+OracleDataWidget.displayName = 'OracleDataWidget';
diff --git a/src/lib/storage.ts b/src/lib/storage.ts
index a36bb4a..e85edb9 100644
--- a/src/lib/storage.ts
+++ b/src/lib/storage.ts
@@ -6,10 +6,24 @@ function get(key: string): string | null {
catch { return null; }
}
-function set(key: string, value: string): void {
- if (!isClient) return;
- try { localStorage.setItem(key, value); }
- catch { /* quota exceeded — ignore */ }
+function isQuotaExceededError(error: unknown): boolean {
+ return error instanceof DOMException && (
+ error.name === 'QuotaExceededError' ||
+ error.name === 'NS_ERROR_DOM_QUOTA_REACHED' ||
+ error.code === 22 ||
+ error.code === 1014
+ );
+}
+
+function set(key: string, value: string): boolean {
+ if (!isClient) return false;
+ try {
+ localStorage.setItem(key, value);
+ return true;
+ } catch (error) {
+ if (isQuotaExceededError(error)) return false;
+ return false;
+ }
}
function remove(key: string): void {
@@ -25,9 +39,9 @@ function getJSON (key: string): T | null {
catch { return null; }
}
-function setJSON(key: string, value: T): void {
- try { set(key, JSON.stringify(value)); }
- catch { /* ignore */ }
+function setJSON(key: string, value: T): boolean {
+ try { return set(key, JSON.stringify(value)); }
+ catch { return false; }
}
function getSession(key: string): string | null {
@@ -36,10 +50,15 @@ function getSession(key: string): string | null {
catch { return null; }
}
-function setSession(key: string, value: string): void {
- if (!isClient) return;
- try { sessionStorage.setItem(key, value); }
- catch { /* quota exceeded — ignore */ }
+function setSession(key: string, value: string): boolean {
+ if (!isClient) return false;
+ try {
+ sessionStorage.setItem(key, value);
+ return true;
+ } catch (error) {
+ if (isQuotaExceededError(error)) return false;
+ return false;
+ }
}
function removeSession(key: string): void {
|