mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-21 15:36:50 -05:00
* Add banners to alert users of recommended budget actions * Update wording for consistency * Release notes * Fix release notes * Code review feedback * Cleanup * Extend playwright timeout * Update Categorize button locator in test * Update VRT * Dummy commit * Streamline cover spending flow * VRT * Remove category from modal when covered and close modal when all categories are covered * Coderabbit suggestions * Update translations * VRT --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
174 lines
4.4 KiB
TypeScript
174 lines
4.4 KiB
TypeScript
import { type Locator, type Page } from '@playwright/test';
|
|
|
|
import { MobileAccountPage } from './mobile-account-page';
|
|
import { MobileAccountsPage } from './mobile-accounts-page';
|
|
import { MobileBudgetPage } from './mobile-budget-page';
|
|
import { MobileReportsPage } from './mobile-reports-page';
|
|
import { MobileTransactionEntryPage } from './mobile-transaction-entry-page';
|
|
import { SettingsPage } from './settings-page';
|
|
|
|
const NAVBAR_ROWS = 3;
|
|
const NAV_LINKS_HIDDEN_BY_DEFAULT = [
|
|
'Reports',
|
|
'Schedules',
|
|
'Payees',
|
|
'Rules',
|
|
'Settings',
|
|
];
|
|
const ROUTES_BY_PAGE = {
|
|
Budget: '/budget',
|
|
Accounts: '/accounts',
|
|
Transaction: '/transactions/new',
|
|
Reports: '/reports',
|
|
Settings: '/settings',
|
|
};
|
|
|
|
export class MobileNavigation {
|
|
readonly page: Page;
|
|
readonly heading: Locator;
|
|
readonly navbar: Locator;
|
|
readonly mainContentSelector: string;
|
|
readonly navbarSelector: string;
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
this.heading = page.getByRole('heading');
|
|
this.navbar = page.getByRole('navigation');
|
|
this.mainContentSelector = '[role=main]';
|
|
this.navbarSelector = '[role=navigation]';
|
|
}
|
|
|
|
async dragNavbarUp() {
|
|
const mainContentBoundingBox = await this.page
|
|
.locator(this.mainContentSelector)
|
|
.boundingBox();
|
|
|
|
if (!mainContentBoundingBox) {
|
|
throw new Error('Unable to get bounding box of main content.');
|
|
}
|
|
|
|
const navbarBoundingBox = await this.page
|
|
.locator(this.navbarSelector)
|
|
.boundingBox();
|
|
|
|
if (!navbarBoundingBox) {
|
|
throw new Error('Unable to get bounding box of navbar.');
|
|
}
|
|
|
|
await this.page.dragAndDrop(this.navbarSelector, this.mainContentSelector, {
|
|
sourcePosition: { x: 1, y: 0 },
|
|
targetPosition: {
|
|
x: 1,
|
|
y: mainContentBoundingBox.height - navbarBoundingBox.height,
|
|
},
|
|
});
|
|
}
|
|
|
|
async dragNavbarDown() {
|
|
const boundingBox = await this.page
|
|
.locator(this.navbarSelector)
|
|
.boundingBox();
|
|
|
|
if (!boundingBox) {
|
|
throw new Error('Unable to get bounding box of navbar.');
|
|
}
|
|
|
|
await this.page.dragAndDrop(this.navbarSelector, this.navbarSelector, {
|
|
sourcePosition: { x: 1, y: 0 },
|
|
targetPosition: {
|
|
x: 1,
|
|
// Only scroll until bottom of screen i.e. bottom of first navbar row.
|
|
y: boundingBox.height / NAVBAR_ROWS,
|
|
},
|
|
});
|
|
}
|
|
|
|
async hasNavbarState(...states: string[]) {
|
|
if ((await this.navbar.count()) === 0) {
|
|
// No navbar on page.
|
|
return false;
|
|
}
|
|
|
|
const dataNavbarState = await this.navbar.getAttribute('data-navbar-state');
|
|
if (!dataNavbarState) {
|
|
throw new Error('Navbar does not have data-navbar-state attribute.');
|
|
}
|
|
return states.includes(dataNavbarState);
|
|
}
|
|
|
|
async navigateToPage<T extends { waitFor: Locator['waitFor'] }>(
|
|
pageName: keyof typeof ROUTES_BY_PAGE,
|
|
pageModelFactory: () => T,
|
|
): Promise<T> {
|
|
const pageInstance = pageModelFactory();
|
|
|
|
if (this.page.url().endsWith(ROUTES_BY_PAGE[pageName])) {
|
|
// Already on the page.
|
|
return pageInstance;
|
|
}
|
|
|
|
await this.navbar.waitFor();
|
|
|
|
const navbarStates = NAV_LINKS_HIDDEN_BY_DEFAULT.includes(pageName)
|
|
? ['default', 'hidden']
|
|
: ['hidden'];
|
|
|
|
if (await this.hasNavbarState(...navbarStates)) {
|
|
await this.dragNavbarUp();
|
|
}
|
|
|
|
const link = this.navbar.getByRole('link', { name: pageName });
|
|
await link.click();
|
|
|
|
await pageInstance.waitFor();
|
|
|
|
if (await this.hasNavbarState('open')) {
|
|
await this.dragNavbarDown();
|
|
}
|
|
|
|
return pageInstance;
|
|
}
|
|
|
|
async goToBudgetPage() {
|
|
return await this.navigateToPage(
|
|
'Budget',
|
|
() => new MobileBudgetPage(this.page),
|
|
);
|
|
}
|
|
|
|
async goToAccountsPage() {
|
|
return await this.navigateToPage(
|
|
'Accounts',
|
|
() => new MobileAccountsPage(this.page),
|
|
);
|
|
}
|
|
|
|
async goToUncategorizedPage() {
|
|
const button = this.page.getByRole('button', { name: 'Categorize' });
|
|
await button.click();
|
|
|
|
return new MobileAccountPage(this.page);
|
|
}
|
|
|
|
async goToTransactionEntryPage() {
|
|
return await this.navigateToPage(
|
|
'Transaction',
|
|
() => new MobileTransactionEntryPage(this.page),
|
|
);
|
|
}
|
|
|
|
async goToReportsPage() {
|
|
return await this.navigateToPage(
|
|
'Reports',
|
|
() => new MobileReportsPage(this.page),
|
|
);
|
|
}
|
|
|
|
async goToSettingsPage() {
|
|
return await this.navigateToPage(
|
|
'Settings',
|
|
() => new SettingsPage(this.page),
|
|
);
|
|
}
|
|
}
|