diff --git a/packages/desktop-client/e2e/mobile.test.js b/packages/desktop-client/e2e/mobile.test.js index 4f68619a60e21780c580386f97f62b76b008b86b..d7fdda7cc46d24d2cb15b2058ecd7ff755044712 100644 --- a/packages/desktop-client/e2e/mobile.test.js +++ b/packages/desktop-client/e2e/mobile.test.js @@ -108,7 +108,7 @@ test.describe('Mobile', () => { test('creates a transaction from `/accounts/:id` page', async () => { const accountsPage = await navigation.goToAccountsPage(); - const accountPage = await accountsPage.openNthAccount(1); + const accountPage = await accountsPage.openNthAccount(2); const transactionEntryPage = await accountPage.clickCreateTransaction(); await expect(transactionEntryPage.header).toHaveText('New Transaction'); diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png index 8898769c0ba9e12add890b8df34f4113489f484e..8b6f637184de2fbee75c5705cb3be3a8d9f49b5d 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png index 7fcb3d43a44783a58204b9c3a6c1bfa5d896b5a5..6747d2985ada002a586322c65fcf3120f99719c6 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-opens-individual-account-page-and-checks-that-filtering-is-working-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/page-models/rules-page.js b/packages/desktop-client/e2e/page-models/rules-page.js index 31926bcecbb5535c421e390ea0f3546b7e56444d..c1228bbd0e14bf768fbe92c0e773bac1d0aed692 100644 --- a/packages/desktop-client/e2e/page-models/rules-page.js +++ b/packages/desktop-client/e2e/page-models/rules-page.js @@ -1,6 +1,7 @@ export class RulesPage { constructor(page) { this.page = page; + this.searchBox = page.getByPlaceholder('Filter rules...'); } /** @@ -22,19 +23,19 @@ export class RulesPage { * Retrieve the data for the nth-rule. * 0-based index */ - async getNthRule(index) { + getNthRule(index) { const row = this.page.getByTestId('table').getByTestId('row').nth(index); return { - conditions: await row - .getByTestId('conditions') - .evaluate(el => [...el.children].map(c => c.textContent)), - actions: await row - .getByTestId('actions') - .evaluate(el => [...el.children].map(c => c.textContent)), + conditions: row.getByTestId('conditions').locator(':scope > div'), + actions: row.getByTestId('actions').locator(':scope > div'), }; } + async searchFor(text) { + await this.searchBox.fill(text); + } + async _fillRuleFields(data) { if (data.conditionsOp) { await this.page diff --git a/packages/desktop-client/e2e/page-models/schedules-page.js b/packages/desktop-client/e2e/page-models/schedules-page.js index 052cd31cb6fe61e6c97cafe6cfeb046bc8072c38..23b2fe26c1f75dbb7be23010cb38f6ba1bb6aaa5 100644 --- a/packages/desktop-client/e2e/page-models/schedules-page.js +++ b/packages/desktop-client/e2e/page-models/schedules-page.js @@ -31,15 +31,15 @@ export class SchedulesPage { * Retrieve the data for the nth-schedule. * 0-based index */ - async getNthSchedule(index) { + getNthSchedule(index) { const row = this.getNthScheduleRow(index); return { - payee: await row.getByTestId('payee').textContent(), - account: await row.getByTestId('account').textContent(), - date: await row.getByTestId('date').textContent(), - status: await row.getByTestId('status').textContent(), - amount: await row.getByTestId('amount').textContent(), + payee: row.getByTestId('payee'), + account: row.getByTestId('account'), + date: row.getByTestId('date'), + status: row.getByTestId('status'), + amount: row.getByTestId('amount'), }; } diff --git a/packages/desktop-client/e2e/rules.test.js b/packages/desktop-client/e2e/rules.test.js index d379d6d3fef486d3edb2ab8e200e2fb4af6a3184..bec8a475e8f565d9e30ee0fb573333064d111418 100644 --- a/packages/desktop-client/e2e/rules.test.js +++ b/packages/desktop-client/e2e/rules.test.js @@ -28,6 +28,7 @@ test.describe('Rules', () => { }); test('checks the page visuals', async () => { + await rulesPage.searchFor('Dominion'); await expect(page).toHaveScreenshot(screenshotConfig(page)); }); @@ -48,13 +49,13 @@ test.describe('Rules', () => { ], }); - expect(await rulesPage.getNthRule(0)).toMatchObject({ - conditions: ['payee is Fast Internet'], - actions: ['set category to General'], - }); + await rulesPage.searchFor('Fast Internet'); + const rule = rulesPage.getNthRule(0); + await expect(rule.conditions).toHaveText(['payee is Fast Internet']); + await expect(rule.actions).toHaveText(['set category to General']); await expect(page).toHaveScreenshot(screenshotConfig(page)); - const accountPage = await navigation.goToAccountPage('Bank of America'); + const accountPage = await navigation.goToAccountPage('HSBC'); await accountPage.createSingleTransaction({ payee: 'Fast Internet', diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png index b99cb9a5dbe684b265533e0ad039863f84ce5ab5..f64c072c9720562d5336764fbadfbbd05014ff3f 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png index 5965ecaa59d8244f18535fe00608009d13713493..e74f3b7f1eb46db57e617ad5de4229821c09355f 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png index 0105e69eb51943c32b68815dfbae99b0ecbc2d09..6eef92136176d4ee2193b2fc8acce5df47be5911 100644 Binary files a/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png and b/packages/desktop-client/e2e/rules.test.js-snapshots/Rules-creates-a-rule-and-makes-sure-it-is-applied-when-creating-a-transaction-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js b/packages/desktop-client/e2e/schedules.test.js index 683ddf9b2a7a4590c266f3db0a5137208e0e7367..16b0ca222b55577ba7833f1fd97ec156d72f3caf 100644 --- a/packages/desktop-client/e2e/schedules.test.js +++ b/packages/desktop-client/e2e/schedules.test.js @@ -38,18 +38,15 @@ test.describe('Schedules', () => { amount: 25, }); - expect(await schedulesPage.getNthSchedule(0)).toMatchObject({ - payee: 'Home Depot', - account: 'HSBC', - amount: '~25.00', - status: 'Due', - }); + const schedule = schedulesPage.getNthSchedule(2); + await expect(schedule.payee).toHaveText('Home Depot'); + await expect(schedule.account).toHaveText('HSBC'); + await expect(schedule.amount).toHaveText('~25.00'); + await expect(schedule.status).toHaveText('Due'); await expect(page).toHaveScreenshot(screenshotConfig(page)); - await schedulesPage.postNthSchedule(0); - expect(await schedulesPage.getNthSchedule(0)).toMatchObject({ - status: 'Paid', - }); + await schedulesPage.postNthSchedule(2); + await expect(schedulesPage.getNthSchedule(2).status).toHaveText('Paid'); await expect(page).toHaveScreenshot(screenshotConfig(page)); // Go to transactions page @@ -62,26 +59,24 @@ test.describe('Schedules', () => { // go to rules page const rulesPage = await navigation.goToRulesPage(); - expect(await rulesPage.getNthRule(0)).toMatchObject({ - // actions: ['link schedule Home Depot (2023-02-28)'], - actions: [ - expect.stringMatching( - /^link schedule Home Depot \(\d{4}-\d{2}-\d{2}\)$/, - ), - ], - conditions: [ - 'payee is Home Depot', - 'and account is HSBC', - expect.stringMatching(/^and date is approx Every month on the/), - 'and amount is approx -25.00', - ], - }); + await rulesPage.searchFor('Home Depot'); + const rule = rulesPage.getNthRule(0); + await expect(rule.actions).toHaveText([ + 'link schedule Home Depot (2017-01-01)', + ]); + await expect(rule.conditions).toHaveText([ + 'payee is Home Depot', + 'and account is HSBC', + 'and date is approx Every month on the 1st', + 'and amount is approx -25.00', + ]); // Go back to schedules page await navigation.goToSchedulesPage(); - await schedulesPage.completeNthSchedule(0); - expect(await schedulesPage.getNthScheduleRow(0)).toHaveText( + await schedulesPage.completeNthSchedule(2); + await expect(schedulesPage.getNthScheduleRow(4)).toHaveText( 'Show completed schedules', ); + await expect(page).toHaveScreenshot(screenshotConfig(page)); }); }); diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png index 9397a30e057116f91b731809cc56dca81db6873b..396415653156e7d8d51184295f876a17d8457a86 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-checks-the-page-visuals-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png index b1b23b44ac4046e2ec3f9461cfd77ab84989669b..42ed28b1b78b7d9fb532f1bdb42bb39774fe55e6 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png index bd6e38b4485610146b2e4a6ed240aeded7ff5684..c061ae744bc3b2993a4a4b88b5d701374023df25 100644 Binary files a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..7aa16653376369549deb0b867c4752c0e788dcb7 Binary files /dev/null and b/packages/desktop-client/e2e/schedules.test.js-snapshots/Schedules-creates-a-new-schedule-posts-the-transaction-and-later-completes-it-3-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/ManageRules.js b/packages/desktop-client/src/components/ManageRules.js index 11c9e3737adefca46491184d1b032ac4a218b10f..ee5c4e9bd42a8117e4bcf2531df1373d65046b0f 100644 --- a/packages/desktop-client/src/components/ManageRules.js +++ b/packages/desktop-client/src/components/ManageRules.js @@ -1,10 +1,4 @@ -import React, { - useState, - useEffect, - useRef, - useCallback, - useMemo, -} from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { pushModal } from 'loot-core/src/client/actions/modals'; @@ -116,7 +110,6 @@ function ManageRulesContent({ isModal, payeeId, setLoading }) { ); let selectedInst = useSelected('manage-rules', allRules, []); let [hoveredRule, setHoveredRule] = useState(null); - let tableRef = useRef(null); async function loadRules() { setLoading(true); @@ -283,7 +276,6 @@ function ManageRulesContent({ isModal, payeeId, setLoading }) { <View style={{ flex: 1 }}> <RulesHeader /> <SimpleTable - ref={tableRef} data={filteredRules} loadMore={loadMore} // Hide the last border of the item in the table diff --git a/packages/desktop-client/src/components/transactions/MobileTransaction.js b/packages/desktop-client/src/components/transactions/MobileTransaction.js index 37fc0c9d359f223cac4d9e7ece297a8acf167b72..e5d7b45b352f09d7f8867b00a4c6672f4faa3767 100644 --- a/packages/desktop-client/src/components/transactions/MobileTransaction.js +++ b/packages/desktop-client/src/components/transactions/MobileTransaction.js @@ -28,6 +28,7 @@ import { send } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { getScheduledAmount } from 'loot-core/src/shared/schedules'; import { + isPreviewId, ungroupTransactions, updateTransaction, realizeTempTransactions, @@ -70,10 +71,6 @@ const zIndices = { SECTION_HEADING: 10 }; let getPayeesById = memoizeOne(payees => groupById(payees)); let getAccountsById = memoizeOne(accounts => groupById(accounts)); -function isPreviewId(id) { - return id.indexOf('preview/') !== -1; -} - function getDescriptionPretty(transaction, payee, transferAcct) { let { amount } = transaction; @@ -1043,7 +1040,9 @@ export class TransactionList extends Component { } sections.push({ - id: transaction.date, + id: `${isPreviewId(transaction.id) ? 'preview/' : ''}${ + transaction.date + }`, date: transaction.date, data: [], }); diff --git a/packages/desktop-client/src/util/versions.ts b/packages/desktop-client/src/util/versions.ts index 00dc03eab3d17ea06f9239ea4b8dff539dfb9dfb..d483aa1543b7412e954e72874d290cb5462bd062 100644 --- a/packages/desktop-client/src/util/versions.ts +++ b/packages/desktop-client/src/util/versions.ts @@ -1,3 +1,5 @@ +import * as Platform from 'loot-core/src/client/platform'; + function parseSemanticVersion(versionString): [number, number, number] { return versionString .replace('v', '') @@ -15,18 +17,32 @@ function cmpSemanticVersion( return x[0] - y[0] || x[1] - y[1] || x[2] - y[2]; } -export async function getLatestVersion(): Promise<string> { - let response = await fetch( - 'https://api.github.com/repos/actualbudget/actual/tags', - ); - let json = await response.json(); - let tags = json.map(t => t.name).concat([`v${window.Actual.ACTUAL_VERSION}`]); - tags.sort(cmpSemanticVersion); +export async function getLatestVersion(): Promise<string | 'unknown'> { + if (Platform.isPlaywright) { + return Promise.resolve('v99.9.9'); + } + + try { + let response = await fetch( + 'https://api.github.com/repos/actualbudget/actual/tags', + ); + let json = await response.json(); + let tags = json + .map(t => t.name) + .concat([`v${window.Actual.ACTUAL_VERSION}`]); + tags.sort(cmpSemanticVersion); - return tags[tags.length - 1]; + return tags[tags.length - 1]; + } catch { + // Rate limit exceeded? Or perhaps Github is down? + return 'unknown'; + } } export async function getIsOutdated(latestVersion: string): Promise<boolean> { let clientVersion = window.Actual.ACTUAL_VERSION; + if (latestVersion === 'unknown') { + return Promise.resolve(false); + } return cmpSemanticVersion(clientVersion, latestVersion) < 0; } diff --git a/packages/loot-core/src/mocks/budget.ts b/packages/loot-core/src/mocks/budget.ts index 0c9e241b99740fa1c5a4f72e35127a0f5cd6bf75..7f53c8ac3f907166cdafc3321eac906ff3444729 100644 --- a/packages/loot-core/src/mocks/budget.ts +++ b/packages/loot-core/src/mocks/budget.ts @@ -731,6 +731,116 @@ export async function createTestBudget(handlers) { await sheet.waitOnSpreadsheet(); + // Create some schedules + await runMutator(() => + batchMessages(async () => { + const account = accounts.find(acc => acc.name === 'Bank of America'); + + await runHandler(handlers['schedule/create'], { + schedule: { + name: 'Phone bills', + posts_transaction: false, + }, + conditions: [ + { + op: 'is', + field: 'payee', + value: payees.find(item => item.name === 'Dominion Power').id, + }, + { + op: 'is', + field: 'account', + value: account.id, + }, + { + op: 'is', + field: 'date', + value: { + start: monthUtils.currentDay(), + frequency: 'monthly', + patterns: [], + skipWeekend: false, + weekendSolveMode: 'after', + }, + }, + { op: 'isapprox', field: 'amount', value: -12000 }, + ], + }); + + await runHandler(handlers['schedule/create'], { + schedule: { + name: 'Internet bill', + posts_transaction: false, + }, + conditions: [ + { + op: 'is', + field: 'payee', + value: payees.find(item => item.name === 'Fast Internet').id, + }, + { + op: 'is', + field: 'account', + value: account.id, + }, + { + op: 'is', + field: 'date', + value: monthUtils.subDays(monthUtils.currentDay(), 1), + }, + { op: 'isapprox', field: 'amount', value: -14000 }, + ], + }); + + await runHandler(handlers['schedule/create'], { + schedule: { + name: 'Wedding', + posts_transaction: false, + }, + conditions: [ + { + op: 'is', + field: 'date', + value: { + start: monthUtils.subDays(monthUtils.currentDay(), 3), + frequency: 'monthly', + patterns: [], + skipWeekend: false, + weekendSolveMode: 'after', + }, + }, + { op: 'is', field: 'amount', value: -2700000 }, + ], + }); + + await runHandler(handlers['schedule/create'], { + schedule: { + name: 'Utilities', + posts_transaction: false, + }, + conditions: [ + { + op: 'is', + field: 'account', + value: account.id, + }, + { + op: 'is', + field: 'date', + value: { + start: monthUtils.addDays(monthUtils.currentDay(), 1), + frequency: 'monthly', + patterns: [], + skipWeekend: false, + weekendSolveMode: 'after', + }, + }, + { op: 'is', field: 'amount', value: -190000 }, + ], + }); + }), + ); + // Create a budget await createBudget(accounts, payees, allGroups); } diff --git a/packages/loot-core/src/server/schedules/app.ts b/packages/loot-core/src/server/schedules/app.ts index 633e9898eaa2aea3063d6c6ceed47c5a06cdb176..a371d4598378fe78238e70143aa9f96e62bba93d 100644 --- a/packages/loot-core/src/server/schedules/app.ts +++ b/packages/loot-core/src/server/schedules/app.ts @@ -212,7 +212,7 @@ export async function createSchedule({ schedule = null, conditions = [], } = {}) { - let scheduleId = (schedule && schedule.id) || uuidv4(); + let scheduleId = schedule?.id || uuidv4(); let { date: dateCond } = extractScheduleConds(conditions); if (dateCond == null) { @@ -235,8 +235,7 @@ export async function createSchedule({ } // Create the rule here based on the info - let ruleId; - ruleId = await insertRule({ + let ruleId = await insertRule({ stage: null, conditionsOp: 'and', conditions, diff --git a/packages/loot-core/src/server/schedules/types/handlers.ts b/packages/loot-core/src/server/schedules/types/handlers.ts index 0992b858aee8d6589e3a0bb65047e8b043821363..f2a00618bfd57442d3ece4314d3a70cd0a39e089 100644 --- a/packages/loot-core/src/server/schedules/types/handlers.ts +++ b/packages/loot-core/src/server/schedules/types/handlers.ts @@ -1,6 +1,10 @@ export interface SchedulesHandlers { 'schedule/create': (arg: { - schedule: unknown; + schedule: { + id?: string; + name?: string; + post_transaction?: boolean; + }; conditions: unknown[]; }) => Promise<string>; diff --git a/upcoming-release-notes/1672.md b/upcoming-release-notes/1672.md new file mode 100644 index 0000000000000000000000000000000000000000..c34fadd4fbfe4938487852548cbcc81c55254949 --- /dev/null +++ b/upcoming-release-notes/1672.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MatissJanis] +--- + +Added mock schedules to the test budget to improve reliability and testing experience