diff --git a/frontend/src/views/accounts/AccountsView.vue b/frontend/src/views/accounts/AccountsView.vue
index 021d974..7bcd34d 100644
--- a/frontend/src/views/accounts/AccountsView.vue
+++ b/frontend/src/views/accounts/AccountsView.vue
@@ -131,29 +131,43 @@ async function handleCreate(data: FormData) {
Accounts
+
+
+
+
+ mdi-cog
+
+
+
+
+
+ mdi-plus
+
+ Add Account
+
+
+
+
+ {{ accountsStore.isEditMode ? 'mdi-check' : 'mdi-pencil' }}
+
+ {{ accountsStore.isEditMode ? 'Done Editing' : 'Edit Order' }}
+
+
+
-
-
- {{ accountsStore.isEditMode ? 'mdi-check' : 'mdi-pencil' }}
-
- {{ accountsStore.isEditMode ? 'Done' : 'Edit Order' }}
-
-
- Add Account
-
diff --git a/frontend/tests/component/accounts/AccountForm.spec.ts b/frontend/tests/component/accounts/AccountForm.spec.ts
index d5a7161..11c9970 100644
--- a/frontend/tests/component/accounts/AccountForm.spec.ts
+++ b/frontend/tests/component/accounts/AccountForm.spec.ts
@@ -35,7 +35,6 @@ describe('AccountForm', () => {
describe('create mode', () => {
it('shows starting balance field', () => {
const { wrapper } = mountComponent()
- expect(wrapper.find('input[type="number"]').exists()).toBe(true)
expect(wrapper.text()).toContain('Starting Balance')
})
@@ -77,7 +76,7 @@ describe('AccountForm', () => {
it('hides starting balance field', () => {
const { wrapper } = mountComponent({ account: existingAccount })
- expect(wrapper.find('input[type="number"]').exists()).toBe(false)
+ expect(wrapper.text()).not.toContain('Starting Balance')
})
it('shows active toggle', () => {
@@ -156,8 +155,11 @@ describe('AccountForm', () => {
const nameInput = wrapper.find('input[type="text"]')
await nameInput.setValue('New Account')
- const balanceInput = wrapper.find('input[type="number"]')
- await balanceInput.setValue('123.45')
+ // MoneyInput uses type="text" with inputmode="numeric" and auto-decimal
+ // Find the MoneyInput's text input (second text input after name)
+ const moneyInput = wrapper.findComponent({ name: 'MoneyInput' })
+ // Simulate typing digits "12345" which MoneyInput formats as "123.45"
+ await moneyInput.vm.$emit('update:modelValue', '123.45')
await nextTick()
await nextTick()
diff --git a/frontend/tests/component/accounts/AccountsReorder.spec.ts b/frontend/tests/component/accounts/AccountsReorder.spec.ts
index 51f82be..02895bf 100644
--- a/frontend/tests/component/accounts/AccountsReorder.spec.ts
+++ b/frontend/tests/component/accounts/AccountsReorder.spec.ts
@@ -51,21 +51,21 @@ function el(wrapper: VueWrapper): HTMLElement {
return wrapper.element as HTMLElement
}
-// Helper: enter edit mode by clicking the edit-order button
+// Helper: enter edit mode via the settings menu
async function enterEditMode(wrapper: VueWrapper) {
- const editBtn = wrapper.find('[data-testid="edit-order-button"]')
- expect(editBtn.exists()).toBe(true)
- await editBtn.trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="edit-order-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
await flushPromises()
await flushPromises()
}
-// Helper: exit edit mode by clicking the done button
+// Helper: exit edit mode via the settings menu
async function exitEditMode(wrapper: VueWrapper) {
- const doneBtn = wrapper.find('[data-testid="edit-order-button"]')
- expect(doneBtn.exists()).toBe(true)
- await doneBtn.trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="edit-order-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
await flushPromises()
}
@@ -76,43 +76,37 @@ describe('AccountsView - Edit Mode / Reordering', () => {
useReorderHandlers()
const { wrapper } = await mountAndSettle()
- // Initially shows "Edit Order" button, no reorder buttons
- const editBtn = wrapper.find('[data-testid="edit-order-button"]')
- expect(editBtn.text()).toContain('Edit Order')
+ // Initially no reorder buttons visible
expect(el(wrapper).querySelector('[data-testid="move-up-button"]')).toBeNull()
// Enter edit mode
await enterEditMode(wrapper)
- // Button text should change to "Done"
- expect(wrapper.find('[data-testid="edit-order-button"]').text()).toContain('Done')
// Reorder buttons should be visible
expect(el(wrapper).querySelector('[data-testid="move-up-button"]')).not.toBeNull()
// Exit edit mode
await exitEditMode(wrapper)
- // Button should revert to "Edit Order"
- expect(wrapper.find('[data-testid="edit-order-button"]').text()).toContain('Edit Order')
// Reorder buttons should be hidden
await flushPromises()
expect(el(wrapper).querySelector('[data-testid="move-up-button"]')).toBeNull()
})
- it('hides add button in edit mode', async () => {
+ it('settings menu remains accessible in edit mode', async () => {
useReorderHandlers()
const { wrapper } = await mountAndSettle()
- // Add button visible initially
- expect(wrapper.find('[data-testid="add-account-button"]').exists()).toBe(true)
+ // Settings menu visible initially
+ expect(wrapper.find('[data-testid="accounts-settings-menu"]').exists()).toBe(true)
- // Enter edit mode - add button should be hidden
+ // Enter edit mode - settings menu should still be visible
await enterEditMode(wrapper)
- expect(wrapper.find('[data-testid="add-account-button"]').exists()).toBe(false)
+ expect(wrapper.find('[data-testid="accounts-settings-menu"]').exists()).toBe(true)
- // Exit edit mode - add button should reappear
+ // Exit edit mode - settings menu should still be visible
await exitEditMode(wrapper)
- expect(wrapper.find('[data-testid="add-account-button"]').exists()).toBe(true)
+ expect(wrapper.find('[data-testid="accounts-settings-menu"]').exists()).toBe(true)
})
})
diff --git a/frontend/tests/component/accounts/AccountsView.spec.ts b/frontend/tests/component/accounts/AccountsView.spec.ts
index 3c6c849..6793938 100644
--- a/frontend/tests/component/accounts/AccountsView.spec.ts
+++ b/frontend/tests/component/accounts/AccountsView.spec.ts
@@ -24,12 +24,11 @@ async function mountAndSettle[0]>(
}
describe('AccountsView', () => {
- it('displays page title and Add Account button', async () => {
+ it('displays page title and settings menu', async () => {
const { wrapper } = await mountAndSettle(AccountsView)
expect(wrapper.find('h1').text()).toBe('Accounts')
- const addBtn = wrapper.find('[data-testid="add-account-button"]')
- expect(addBtn.exists()).toBe(true)
- expect(addBtn.text()).toContain('Add Account')
+ const settingsBtn = wrapper.find('[data-testid="accounts-settings-menu"]')
+ expect(settingsBtn.exists()).toBe(true)
})
it('shows empty state when no accounts', async () => {
@@ -48,7 +47,9 @@ describe('AccountsView', () => {
const { wrapper } = await mountAndSettle(AccountsView, { attachTo: document.body })
// Open create dialog
- await wrapper.find('[data-testid="add-account-button"]').trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="add-account-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
// Dialog should show
@@ -77,7 +78,9 @@ describe('AccountsView', () => {
it('creates a savings account with description', async () => {
const { wrapper } = await mountAndSettle(AccountsView, { attachTo: document.body })
- await wrapper.find('[data-testid="add-account-button"]').trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="add-account-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
// Fill name
@@ -95,7 +98,9 @@ describe('AccountsView', () => {
it('creates a credit card account', async () => {
const { wrapper } = await mountAndSettle(AccountsView, { attachTo: document.body })
- await wrapper.find('[data-testid="add-account-button"]').trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="add-account-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
// Verify the account type select exists with options
@@ -108,7 +113,9 @@ describe('AccountsView', () => {
it('shows validation error for empty name (disabled button)', async () => {
const { wrapper } = await mountAndSettle(AccountsView, { attachTo: document.body })
- await wrapper.find('[data-testid="add-account-button"]').trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="add-account-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
// Create button should be disabled (no name entered)
@@ -124,7 +131,9 @@ describe('AccountsView', () => {
it('creates account with icon from picker', async () => {
const { wrapper } = await mountAndSettle(AccountsView, { attachTo: document.body })
- await wrapper.find('[data-testid="add-account-button"]').trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="add-account-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
// Icon chips should be visible
@@ -142,7 +151,9 @@ describe('AccountsView', () => {
it('creates account with custom emoji', async () => {
const { wrapper } = await mountAndSettle(AccountsView, { attachTo: document.body })
- await wrapper.find('[data-testid="add-account-button"]').trigger('click')
+ await wrapper.find('[data-testid="accounts-settings-menu"]').trigger('click')
+ await flushPromises()
+ await document.querySelector('[data-testid="add-account-menu-item"]')?.dispatchEvent(new Event('click', { bubbles: true }))
await flushPromises()
// Custom emoji input should exist
diff --git a/frontend/tests/e2e/pages/accounts.page.ts b/frontend/tests/e2e/pages/accounts.page.ts
index 7776634..051c8b4 100644
--- a/frontend/tests/e2e/pages/accounts.page.ts
+++ b/frontend/tests/e2e/pages/accounts.page.ts
@@ -7,8 +7,9 @@ import type { AccountType } from '../fixtures/test-data'
*/
export class AccountsPage extends BasePage {
// Header elements
- readonly addAccountButton: Locator
- readonly editOrderButton: Locator
+ readonly settingsMenuButton: Locator
+ readonly addAccountMenuItem: Locator
+ readonly editOrderMenuItem: Locator
// Account list sections
readonly allAccountsRow: Locator
@@ -37,8 +38,9 @@ export class AccountsPage extends BasePage {
super(page)
// Header buttons
- this.addAccountButton = page.locator('[data-testid="add-account-button"]')
- this.editOrderButton = page.locator('[data-testid="edit-order-button"]')
+ this.settingsMenuButton = page.locator('[data-testid="accounts-settings-menu"]')
+ this.addAccountMenuItem = page.locator('[data-testid="add-account-menu-item"]')
+ this.editOrderMenuItem = page.locator('[data-testid="edit-order-menu-item"]')
// Account list sections (scoped to main content to avoid matching sidebar)
const mainContent = page.locator('main, [role="main"]')
@@ -59,7 +61,7 @@ export class AccountsPage extends BasePage {
this.customIconInput = this.createDialog.locator('[data-testid="custom-icon-input"] input')
this.useDefaultIconButton = this.createDialog.locator('[data-testid="use-default-icon-button"]')
this.descriptionInput = this.createDialog.locator('textarea')
- this.startingBalanceInput = this.createDialog.locator('input[type="number"]')
+ this.startingBalanceInput = this.createDialog.locator('input[inputmode="numeric"]')
this.includeInBudgetSwitch = this.createDialog.locator('.v-switch').filter({ hasText: 'Include in budget' })
this.createButton = this.createDialog.getByRole('button', { name: 'Create' })
this.cancelButton = this.createDialog.getByRole('button', { name: 'Cancel' })
@@ -74,7 +76,8 @@ export class AccountsPage extends BasePage {
* Open the create account dialog.
*/
async openCreateDialog() {
- await this.addAccountButton.click()
+ await this.settingsMenuButton.click()
+ await this.addAccountMenuItem.click()
await expect(this.createDialog).toBeVisible()
}
@@ -270,12 +273,13 @@ export class AccountsPage extends BasePage {
* Check if currently in edit mode.
*/
async isInEditMode(): Promise {
- const buttonText = await this.editOrderButton.textContent()
- return buttonText?.includes('Done') ?? false
+ // Check for reorder buttons being visible as the indicator of edit mode
+ const moveUpButton = this.page.locator('[data-testid="move-up-button"]').first()
+ return await moveUpButton.isVisible().catch(() => false)
}
/**
- * Enter edit mode by clicking the Edit Order button.
+ * Enter edit mode via the settings menu.
*/
async enterEditMode() {
if (await this.isInEditMode()) return
@@ -285,33 +289,33 @@ export class AccountsPage extends BasePage {
.locator('[data-testid="budget-account-item"], [data-testid="tracking-account-item"]')
.first()
.waitFor({ state: 'visible', timeout: 10000 })
- await this.editOrderButton.click()
- await expect(this.editOrderButton).toContainText('Done')
+ await this.settingsMenuButton.click()
+ await this.editOrderMenuItem.click()
+ await expect(this.page.locator('[data-testid="move-up-button"]').first()).toBeVisible()
}
/**
- * Exit edit mode by clicking the Done button.
+ * Exit edit mode via the settings menu.
*/
async exitEditMode() {
if (!(await this.isInEditMode())) return
- await this.editOrderButton.click()
- await expect(this.editOrderButton).toContainText('Edit Order')
+ await this.settingsMenuButton.click()
+ await this.editOrderMenuItem.click()
+ await expect(this.page.locator('[data-testid="move-up-button"]').first()).toBeHidden()
}
/**
* Assert we are in edit mode.
*/
async expectInEditMode() {
- await expect(this.editOrderButton).toContainText('Done')
- await expect(this.addAccountButton).toBeHidden()
+ await expect(this.page.locator('[data-testid="move-up-button"]').first()).toBeVisible()
}
/**
* Assert we are not in edit mode.
*/
async expectNotInEditMode() {
- await expect(this.editOrderButton).toContainText('Edit Order')
- await expect(this.addAccountButton).toBeVisible()
+ await expect(this.page.locator('[data-testid="move-up-button"]')).toHaveCount(0)
}
/**