diff --git a/resources/js/pages/settings/connections.test.tsx b/resources/js/pages/settings/connections.test.tsx new file mode 100644 index 00000000..ba1b7b0f --- /dev/null +++ b/resources/js/pages/settings/connections.test.tsx @@ -0,0 +1,112 @@ +import type { BankingConnection } from '@/types/banking'; +import { render, screen } from '@testing-library/react'; +import type React from 'react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import ConnectionsPage from './connections'; + +const mocks = vi.hoisted(() => ({ + routerPost: vi.fn(), + routerVisit: vi.fn(), + pollStart: vi.fn(), + pollStop: vi.fn(), +})); + +vi.mock('@inertiajs/react', () => ({ + Head: ({ title }: { title: string }) => {title}, + router: { + post: mocks.routerPost, + visit: mocks.routerVisit, + }, + usePage: () => ({ + props: { + auth: { + isDemoAccount: false, + hasProPlan: true, + }, + flash: {}, + subscriptionsEnabled: false, + }, + }), + usePoll: () => ({ + start: mocks.pollStart, + stop: mocks.pollStop, + }), +})); + +vi.mock('@/layouts/app-layout', () => ({ + default: ({ children }: { children: React.ReactNode }) => <>{children}, +})); + +vi.mock('@/layouts/settings/layout', () => ({ + default: ({ children }: { children: React.ReactNode }) => <>{children}, +})); + +vi.mock('@/components/open-banking/connect-account-dialog', () => ({ + ConnectAccountDialog: () => null, +})); + +vi.mock('@/components/open-banking/disconnect-dialog', () => ({ + DisconnectDialog: () => null, +})); + +vi.mock('@/components/open-banking/update-credentials-dialog', () => ({ + UpdateCredentialsDialog: () => null, +})); + +vi.mock('@/components/open-banking/upgrade-connection-dialog', () => ({ + UpgradeConnectionDialog: () => null, +})); + +vi.mock('@/components/open-banking/connection-status-badge', () => ({ + ConnectionStatusBadge: ({ status }: { status: string }) => ( + {status} + ), +})); + +function makeConnection( + overrides: Partial = {}, +): BankingConnection { + return { + id: 'connection-1', + provider: 'enablebanking', + aspsp_name: 'Test Bank', + aspsp_country: 'ES', + status: 'active', + valid_until: null, + last_synced_at: '2026-01-01T00:00:00.000000Z', + error_message: null, + accounts_count: 1, + created_at: '2026-01-01T00:00:00.000000Z', + updated_at: '2026-01-01T00:00:00.000000Z', + ...overrides, + }; +} + +describe('ConnectionsPage', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('shows visible reconnect buttons for expired EnableBanking connections', () => { + render( + , + ); + + expect( + screen.getAllByRole('button', { name: /reconnect/i }), + ).toHaveLength(2); + }); +}); diff --git a/resources/js/pages/settings/connections.tsx b/resources/js/pages/settings/connections.tsx index d4b825c1..44af3e82 100644 --- a/resources/js/pages/settings/connections.tsx +++ b/resources/js/pages/settings/connections.tsx @@ -149,6 +149,14 @@ export default function ConnectionsPage({ connections }: Props) { ); } + function canReconnect(connection: BankingConnection): boolean { + return ( + connection.provider === 'enablebanking' && + (connection.status === 'expired' || + isEnableBankingAuthError(connection)) + ); + } + function formatDate(dateString: string | null): string { if (!dateString) return __('Never'); return new Date(dateString).toLocaleDateString(undefined, { @@ -224,6 +232,29 @@ export default function ConnectionsPage({ connections }: Props) { connection.last_synced_at } /> + {connection.status === 'expired' && + canReconnect(connection) && ( + + )}