fix(connections): show expired reconnect (#407)
## Summary - show primary Reconnect action for expired EnableBanking connections - keep dropdown reconnect available for expired connections - add React test covering multiple expired connections ## Tests - npm test -- resources/js/pages/settings/connections.test.tsx - npx eslint resources/js/pages/settings/connections.tsx resources/js/pages/settings/connections.test.tsx ## Notes - npm run types -- --pretty false still fails on pre-existing unrelated TypeScript errors
This commit is contained in:
parent
11f989d03a
commit
d2e00f14e5
|
|
@ -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>{title}</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 }) => (
|
||||
<span>{status}</span>
|
||||
),
|
||||
}));
|
||||
|
||||
function makeConnection(
|
||||
overrides: Partial<BankingConnection> = {},
|
||||
): 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(
|
||||
<ConnectionsPage
|
||||
connections={[
|
||||
makeConnection({
|
||||
id: 'connection-1',
|
||||
aspsp_name: 'First Bank',
|
||||
status: 'expired',
|
||||
}),
|
||||
makeConnection({
|
||||
id: 'connection-2',
|
||||
aspsp_name: 'Second Bank',
|
||||
status: 'expired',
|
||||
}),
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: /reconnect/i }),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -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) && (
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={
|
||||
reconnectingId ===
|
||||
connection.id
|
||||
}
|
||||
onClick={() =>
|
||||
handleReconnect(
|
||||
connection,
|
||||
)
|
||||
}
|
||||
>
|
||||
{reconnectingId ===
|
||||
connection.id ? (
|
||||
<Spinner className="mr-1.5 size-3" />
|
||||
) : (
|
||||
<RotateCcw className="mr-1.5 h-3 w-3" />
|
||||
)}
|
||||
{__('Reconnect')}
|
||||
</Button>
|
||||
)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
|
|
@ -263,7 +294,7 @@ export default function ConnectionsPage({ connections }: Props) {
|
|||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{isEnableBankingAuthError(
|
||||
{canReconnect(
|
||||
connection,
|
||||
) && (
|
||||
<DropdownMenuItem
|
||||
|
|
|
|||
Loading…
Reference in New Issue