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:
parent
9328cd3e1b
commit
9a20335c6a
|
|
@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue