mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-21 15:36:50 -05:00
* Dummy commit * Delete js snapshots * Move extended expect and test to fixtures * Fix wrong commit * Update VRT * Dummy commit to run GH actions * Convert test page models to TS * Release notes * Fix typecheck errors * New page models to TS * Fix typecheck error * Fix page name * Put awaits on getTableTotals --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
412 lines
13 KiB
TypeScript
412 lines
13 KiB
TypeScript
import { type Page } from '@playwright/test';
|
|
|
|
import * as monthUtils from 'loot-core/shared/months';
|
|
import { amountToCurrency, currencyToAmount } from 'loot-core/shared/util';
|
|
|
|
import { expect, test } from './fixtures';
|
|
import { ConfigurationPage } from './page-models/configuration-page';
|
|
import { type MobileBudgetPage } from './page-models/mobile-budget-page';
|
|
import { MobileNavigation } from './page-models/mobile-navigation';
|
|
|
|
const copyLastMonthBudget = async (
|
|
budgetPage: MobileBudgetPage,
|
|
categoryName: string,
|
|
) => {
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
await budgetMenuModal.copyLastMonthBudget();
|
|
await budgetMenuModal.close();
|
|
};
|
|
|
|
const setTo3MonthAverage = async (
|
|
budgetPage: MobileBudgetPage,
|
|
categoryName: string,
|
|
) => {
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
await budgetMenuModal.setTo3MonthAverage();
|
|
await budgetMenuModal.close();
|
|
};
|
|
|
|
const setTo6MonthAverage = async (
|
|
budgetPage: MobileBudgetPage,
|
|
categoryName: string,
|
|
) => {
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
await budgetMenuModal.setTo6MonthAverage();
|
|
await budgetMenuModal.close();
|
|
};
|
|
|
|
const setToYearlyAverage = async (
|
|
budgetPage: MobileBudgetPage,
|
|
categoryName: string,
|
|
) => {
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
await budgetMenuModal.setToYearlyAverage();
|
|
await budgetMenuModal.close();
|
|
};
|
|
|
|
async function setBudgetAverage(
|
|
budgetPage: MobileBudgetPage,
|
|
categoryName: string,
|
|
numberOfMonths: number,
|
|
setBudgetAverageFn: (
|
|
budgetPage: MobileBudgetPage,
|
|
categoryName: string,
|
|
numberOfMonths: number,
|
|
) => Promise<void>,
|
|
) {
|
|
let totalSpent = 0;
|
|
|
|
for (let i = 0; i < numberOfMonths; i++) {
|
|
await budgetPage.goToPreviousMonth();
|
|
const spentButton = await budgetPage.getButtonForSpent(categoryName);
|
|
const spent = await spentButton.textContent();
|
|
if (!spent) {
|
|
throw new Error('Failed to get spent amount');
|
|
}
|
|
totalSpent += currencyToAmount(spent) ?? 0;
|
|
}
|
|
|
|
// Calculate average amount
|
|
const averageSpent = totalSpent / numberOfMonths;
|
|
|
|
// Go back to the current month
|
|
for (let i = 0; i < numberOfMonths; i++) {
|
|
await budgetPage.goToNextMonth();
|
|
}
|
|
|
|
await setBudgetAverageFn(budgetPage, categoryName, numberOfMonths);
|
|
|
|
return averageSpent;
|
|
}
|
|
|
|
const budgetTypes = ['Envelope', 'Tracking'] as const;
|
|
|
|
budgetTypes.forEach(budgetType => {
|
|
test.describe(`Mobile Budget [${budgetType}]`, () => {
|
|
let page: Page;
|
|
let navigation: MobileNavigation;
|
|
let configurationPage: ConfigurationPage;
|
|
let previousGlobalIsTesting: boolean;
|
|
|
|
test.beforeAll(() => {
|
|
// TODO: Hack, properly mock the currentMonth function
|
|
previousGlobalIsTesting = global.IS_TESTING;
|
|
global.IS_TESTING = true;
|
|
});
|
|
|
|
test.afterAll(() => {
|
|
// TODO: Hack, properly mock the currentMonth function
|
|
global.IS_TESTING = previousGlobalIsTesting;
|
|
});
|
|
|
|
test.beforeEach(async ({ browser }) => {
|
|
page = await browser.newPage();
|
|
navigation = new MobileNavigation(page);
|
|
configurationPage = new ConfigurationPage(page);
|
|
|
|
await page.setViewportSize({
|
|
width: 350,
|
|
height: 600,
|
|
});
|
|
await page.goto('/');
|
|
await configurationPage.createTestFile();
|
|
|
|
const settingsPage = await navigation.goToSettingsPage();
|
|
await settingsPage.useBudgetType(budgetType);
|
|
});
|
|
|
|
test.afterEach(async () => {
|
|
await page.close();
|
|
});
|
|
|
|
test('loads the budget page with budgeted amounts', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
await expect(budgetPage.categoryNames).toHaveText([
|
|
'Food',
|
|
'Restaurants',
|
|
'Entertainment',
|
|
'Clothing',
|
|
'General',
|
|
'Gift',
|
|
'Medical',
|
|
'Savings',
|
|
'Cell',
|
|
'Internet',
|
|
'Mortgage',
|
|
'Water',
|
|
'Power',
|
|
'Starting Balances',
|
|
'Misc',
|
|
'Income',
|
|
]);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
// Page Header Tests
|
|
|
|
test('checks that clicking the Actual logo in the page header opens the budget page menu', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
await budgetPage.openBudgetPageMenu();
|
|
|
|
const budgetPageMenuModal = page.getByRole('dialog');
|
|
|
|
await expect(budgetPageMenuModal).toBeVisible();
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
test("checks that clicking the left arrow in the page header shows the previous month's budget", async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const selectedMonth = await budgetPage.getSelectedMonth();
|
|
const displayMonth = monthUtils.format(
|
|
selectedMonth,
|
|
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
|
);
|
|
|
|
await expect(budgetPage.heading).toHaveText(displayMonth);
|
|
|
|
const previousMonth = await budgetPage.goToPreviousMonth();
|
|
const previousDisplayMonth = monthUtils.format(
|
|
previousMonth,
|
|
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
|
);
|
|
|
|
await expect(budgetPage.heading).toHaveText(previousDisplayMonth);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
test('checks that clicking the month in the page header opens the month menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const selectedMonth = await budgetPage.getSelectedMonth();
|
|
|
|
await budgetPage.openMonthMenu();
|
|
|
|
const monthMenuModal = page.getByRole('dialog');
|
|
const monthMenuModalHeading = monthMenuModal.getByRole('heading');
|
|
|
|
const displayMonth = monthUtils.format(
|
|
selectedMonth,
|
|
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
|
);
|
|
await expect(monthMenuModalHeading).toHaveText(displayMonth);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
test("checks that clicking the right arrow in the page header shows the next month's budget", async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const selectedMonth = await budgetPage.getSelectedMonth();
|
|
const displayMonth = monthUtils.format(
|
|
selectedMonth,
|
|
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
|
);
|
|
|
|
await expect(budgetPage.heading).toHaveText(displayMonth);
|
|
|
|
const nextMonth = await budgetPage.goToNextMonth();
|
|
const nextDisplayMonth = monthUtils.format(
|
|
nextMonth,
|
|
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
|
);
|
|
|
|
await expect(budgetPage.heading).toHaveText(nextDisplayMonth);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
// Category / Category Group Menu Tests
|
|
|
|
test('checks that clicking the category group name opens the category group menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryGroupName = await budgetPage.getCategoryGroupNameForRow(0);
|
|
await budgetPage.openCategoryGroupMenu(categoryGroupName);
|
|
|
|
const categoryMenuModalHeading = page
|
|
.getByRole('dialog')
|
|
.getByRole('heading');
|
|
|
|
await expect(categoryMenuModalHeading).toHaveText(categoryGroupName);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
test('checks that clicking the category name opens the category menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
|
const categoryMenuModal = await budgetPage.openCategoryMenu(categoryName);
|
|
|
|
await expect(categoryMenuModal.heading).toHaveText(categoryName);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
// Budgeted Cell Tests
|
|
|
|
test('checks that clicking the budgeted cell opens the budget menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
|
|
await expect(budgetMenuModal.heading).toHaveText(categoryName);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
test('updates the budgeted amount', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
|
|
const budgetAmount = 123;
|
|
|
|
// Set to 123.00
|
|
await budgetMenuModal.setBudgetAmount(`${budgetAmount}00`);
|
|
|
|
const budgetedButton =
|
|
await budgetPage.getButtonForBudgeted(categoryName);
|
|
|
|
await expect(budgetedButton).toHaveText(amountToCurrency(budgetAmount));
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
test(`copies last month's budget`, async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(3);
|
|
const budgetedButton =
|
|
await budgetPage.getButtonForBudgeted(categoryName);
|
|
|
|
await budgetPage.goToPreviousMonth();
|
|
|
|
const lastMonthBudget = await budgetedButton.textContent();
|
|
|
|
if (!lastMonthBudget) {
|
|
throw new Error('Failed to get last month budget');
|
|
}
|
|
|
|
await budgetPage.goToNextMonth();
|
|
|
|
await copyLastMonthBudget(budgetPage, categoryName);
|
|
|
|
await expect(budgetedButton).toHaveText(lastMonthBudget);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
(
|
|
[
|
|
[3, setTo3MonthAverage],
|
|
[6, setTo6MonthAverage],
|
|
[12, setToYearlyAverage],
|
|
] as const
|
|
).forEach(([numberOfMonths, setBudgetAverageFn]) => {
|
|
test(`set budget to ${numberOfMonths} month average`, async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(3);
|
|
|
|
const averageSpent = await setBudgetAverage(
|
|
budgetPage,
|
|
categoryName,
|
|
numberOfMonths,
|
|
setBudgetAverageFn,
|
|
);
|
|
|
|
const budgetedButton =
|
|
await budgetPage.getButtonForBudgeted(categoryName);
|
|
|
|
await expect(budgetedButton).toHaveText(
|
|
amountToCurrency(Math.abs(averageSpent)),
|
|
);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
});
|
|
|
|
test(`applies budget template`, async () => {
|
|
const settingsPage = await navigation.goToSettingsPage();
|
|
await settingsPage.enableExperimentalFeature('Goal templates');
|
|
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(1);
|
|
|
|
const amountToTemplate = 123;
|
|
|
|
const categoryMenuModal = await budgetPage.openCategoryMenu(categoryName);
|
|
const editNotesModal = await categoryMenuModal.editNotes();
|
|
const templateNotes = `#template ${amountToTemplate}`;
|
|
await editNotesModal.updateNotes(templateNotes);
|
|
await editNotesModal.close();
|
|
|
|
const budgetedButton =
|
|
await budgetPage.getButtonForBudgeted(categoryName);
|
|
|
|
const budgetMenuModal = await budgetPage.openBudgetMenu(categoryName);
|
|
await budgetMenuModal.applyBudgetTemplate();
|
|
await budgetMenuModal.close();
|
|
|
|
await expect(budgetedButton).toHaveText(
|
|
amountToCurrency(amountToTemplate),
|
|
);
|
|
const notification = page.getByRole('alert').first();
|
|
await expect(notification).toContainText(templateNotes);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
// Spent Cell Tests
|
|
|
|
test('checks that clicking spent cell redirects to the category transactions page', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
|
const accountPage = await budgetPage.openSpentPage(categoryName);
|
|
|
|
await expect(accountPage.heading).toContainText(categoryName);
|
|
await expect(accountPage.transactionList).toBeVisible();
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
// Balance Cell Tests
|
|
|
|
test('checks that clicking the balance cell opens the balance menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
|
const balanceMenuModal = await budgetPage.openBalanceMenu(categoryName);
|
|
|
|
await expect(balanceMenuModal.heading).toHaveText(categoryName);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
|
|
if (budgetType === 'Envelope') {
|
|
test('checks that clicking the To Budget/Overbudgeted amount opens the budget summary menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const envelopeBudgetSummaryModal =
|
|
await budgetPage.openEnvelopeBudgetSummary();
|
|
|
|
await expect(envelopeBudgetSummaryModal.heading).toHaveText(
|
|
'Budget Summary',
|
|
);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
}
|
|
|
|
if (budgetType === 'Tracking') {
|
|
test('checks that clicking the Saved/Projected Savings/Overspent amount opens the budget summary menu modal', async () => {
|
|
const budgetPage = await navigation.goToBudgetPage();
|
|
|
|
const trackingBudgetSummaryModal =
|
|
await budgetPage.openTrackingBudgetSummary();
|
|
|
|
await expect(trackingBudgetSummaryModal.heading).toHaveText(
|
|
'Budget Summary',
|
|
);
|
|
await expect(page).toMatchThemeScreenshots();
|
|
});
|
|
}
|
|
});
|
|
});
|