fix(transactions): only auto-select account on account pages (#549)

## What

When the add/edit transaction dialog resets in **create** mode, it no
longer auto-selects the first transactional account. The account field
starts empty so the user must pick one deliberately.

The only exception is the account detail page (`accounts/{uuid}`), which
passes `initialAccountId`; there the dialog still pre-selects that
account.

## Why

Auto-selecting the first account made it easy to accidentally book a
transaction to the wrong account. Forcing a deliberate pick (except
where the account is unambiguous from context) avoids that.

## How

- `edit-transaction-dialog.tsx`: dropped the `availableAccounts[0]`
fallback. The field now resolves to `initialAccount?.id ?? ''`.
- The existing submit guard (`Account is required`) already covers the
empty case.
- `Accounts/Show.tsx` already passes `initialAccountId={account.id}`, so
the account-page behavior is unchanged; the transactions list passes
nothing, so it now starts empty.

## Tests

Added two unit tests to `edit-transaction-dialog.test.tsx`:
- no `initialAccountId` → account value stays empty (no auto-select)
- matching `initialAccountId` → account is pre-selected
This commit is contained in:
Víctor Falcón 2026-06-17 13:28:31 +02:00 committed by GitHub
parent 9328cd3e1b
commit 9a20335c6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 106 additions and 6 deletions

View File

@ -52,6 +52,32 @@ vi.mock('sonner', () => ({
},
}));
vi.mock('@/components/ui/select', () => ({
Select: ({
value,
children,
}: {
value?: string;
children: React.ReactNode;
}) => (
<div data-testid="account-value" data-value={value ?? ''}>
{children}
</div>
),
SelectTrigger: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
SelectContent: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
SelectItem: ({ children }: { children: React.ReactNode }) => (
<div>{children}</div>
),
SelectValue: ({ placeholder }: { placeholder?: string }) => (
<span>{placeholder}</span>
),
}));
vi.mock('@/components/ui/dialog', () => ({
Dialog: ({
children,
@ -79,6 +105,11 @@ vi.mock('@/components/ui/dialog', () => ({
describe('EditTransactionDialog', () => {
beforeEach(() => {
globalThis.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
Object.defineProperty(globalThis, 'localStorage', {
value: {
getItem: vi.fn(() => null),
@ -128,4 +159,60 @@ describe('EditTransactionDialog', () => {
expect(screen.getByText('Debtor')).toBeInTheDocument();
expect(screen.getByDisplayValue('Victor Falcon')).toBeDisabled();
});
const checkingAccount = {
id: 'account-1',
name: 'Checking',
name_iv: null,
encrypted: false,
bank: null,
type: 'checking' as const,
currency_code: 'EUR',
banking_connection_id: null,
external_account_id: null,
linked_at: null,
};
it('does not auto-select an account when no initialAccountId is given', () => {
render(
<EditTransactionDialog
transaction={null}
categories={[]}
accounts={[checkingAccount]}
banks={[]}
labels={[]}
open
onOpenChange={vi.fn()}
onSuccess={vi.fn()}
mode="create"
/>,
);
expect(screen.getByTestId('account-value')).toHaveAttribute(
'data-value',
'',
);
});
it('auto-selects the account matching initialAccountId (account page)', () => {
render(
<EditTransactionDialog
transaction={null}
categories={[]}
accounts={[checkingAccount]}
banks={[]}
labels={[]}
open
onOpenChange={vi.fn()}
onSuccess={vi.fn()}
mode="create"
initialAccountId="account-1"
/>,
);
expect(screen.getByTestId('account-value')).toHaveAttribute(
'data-value',
'account-1',
);
});
});

View File

@ -129,12 +129,7 @@ export function EditTransactionDialog({
const initialAccount = availableAccounts.find(
(account) => account.id === initialAccountId,
);
setAccountId(
initialAccount?.id ??
(availableAccounts.length > 0
? availableAccounts[0].id
: ''),
);
setAccountId(initialAccount?.id ?? '');
setCategoryId('null');
setSelectedLabelIds([]);
setNotes('');

View File

@ -47,6 +47,7 @@ it('can create a transaction with amount input', function () {
$category = Category::factory()->create(['user_id' => $user->id]);
$account = Account::factory()->create([
'user_id' => $user->id,
'name' => 'Test Checking',
'currency_code' => 'USD',
'type' => 'checking',
]);
@ -65,6 +66,11 @@ it('can create a transaction with amount input', function () {
->wait(0.5)
->fill('#amount', '123.45')
->wait(0.5)
->click('[data-testid="account-select"]')
->wait(2)
->waitForText('Test Checking', 5)
->click('[role="option"]:has-text("Test Checking")')
->wait(0.5)
->click('[data-testid="category-select"]')
->wait(1)
->click($category->name)
@ -89,6 +95,7 @@ it('formats amount when pressing enter', function () {
$category = Category::factory()->create(['user_id' => $user->id]);
$account = Account::factory()->create([
'user_id' => $user->id,
'name' => 'Test Checking',
'currency_code' => 'USD',
'type' => 'checking',
]);
@ -104,6 +111,11 @@ it('formats amount when pressing enter', function () {
->fill('#description', 'Test Transaction Enter')
->fill('#amount', '99.99')
->wait(0.5)
->click('[data-testid="account-select"]')
->wait(2)
->waitForText('Test Checking', 5)
->click('[role="option"]:has-text("Test Checking")')
->wait(0.5)
->click('[data-testid="category-select"]')
->wait(0.5)
->click($category->name)
@ -127,6 +139,7 @@ it('accepts negative amounts', function () {
$category = Category::factory()->create(['user_id' => $user->id]);
$account = Account::factory()->create([
'user_id' => $user->id,
'name' => 'Test Checking',
'currency_code' => 'USD',
'type' => 'checking',
]);
@ -141,6 +154,11 @@ it('accepts negative amounts', function () {
->wait(1)
->fill('#description', 'Test Negative Amount')
->fill('#amount', '-50.00')
->click('[data-testid="account-select"]')
->wait(2)
->waitForText('Test Checking', 5)
->click('[role="option"]:has-text("Test Checking")')
->wait(0.5)
->click('[data-testid="category-select"]')
->wait(0.5)
->click($category->name)