Compare commits

..

28 Commits

Author SHA1 Message Date
Leandro Menezes
83cd364c5f Changed to remove sessionStorage 2025-06-15 13:56:29 -03:00
Crazypkr1099
b61b1758d6 Merge branch 'master' into scrollToLocationBudget 2024-06-20 21:53:37 -04:00
Crazypkr
dbc434c84e Renamed scrollToPosition to setScrollPosition 2024-06-20 21:50:53 -04:00
Crazypkr
b1ea639e11 Merge branch 'scrollToLocationBudget' of https://github.com/Crazypkr1099/actual into scrollToLocationBudget 2024-06-20 21:47:07 -04:00
Crazypkr
bc6098fbb3 Remove intersectionboundary 2024-06-20 21:47:04 -04:00
Crazypkr1099
15869eca61 Merge branch 'master' into scrollToLocationBudget 2024-06-19 21:59:42 -04:00
Crazypkr1099
c5c098ea0c Merge branch 'master' into scrollToLocationBudget 2024-06-18 18:38:02 -04:00
Crazypkr1099
f6b88cc1ba Merge branch 'master' into scrollToLocationBudget 2024-06-18 16:03:47 -04:00
Crazypkr1099
ca1d067921 Merge branch 'master' into scrollToLocationBudget 2024-06-18 13:55:14 -04:00
Crazypkr1099
18e55800e4 Merge branch 'master' into scrollToLocationBudget 2024-06-18 12:36:52 -04:00
Crazypkr1099
18314acd25 Merge branch 'master' into scrollToLocationBudget 2024-06-14 18:44:32 -04:00
Crazypkr1099
042058ec7b Merge branch 'scrollToLocationBudget' of https://github.com/Crazypkr1099/actual into scrollToLocationBudget 2024-06-12 22:43:01 -04:00
Crazypkr1099
9580be7bc4 Mobile Support 2024-06-12 22:42:58 -04:00
Crazypkr1099
569b995278 Update 2859.md 2024-06-12 19:12:39 -04:00
Crazypkr1099
9590a93e9f Merge branch 'master' into scrollToLocationBudget 2024-06-12 19:12:01 -04:00
Crazypkr1099
c20ebd9dbd Added rollover and reset when leaving budget page 2024-06-12 19:10:50 -04:00
Crazypkr
112f066b8b Fix typescript mistake and changed to sessionStorage 2024-06-09 07:53:45 -04:00
Crazypkr
cf6825a541 bug fixes 2024-06-08 12:27:37 -04:00
Crazypkr
9ec0bdec33 fixing more issues 2024-06-08 12:10:31 -04:00
Crazypkr
4c57596117 linting fix 2024-06-08 12:04:26 -04:00
Crazypkr1099
9e7ebb405f Merge branch 'master' into scrollToLocationBudget 2024-06-08 11:57:25 -04:00
Crazypkr
7ba3a37ead Merge branch 'scrollToLocationBudget' of https://github.com/Crazypkr1099/actual into scrollToLocationBudget 2024-06-08 11:55:13 -04:00
Crazypkr
201e1dab54 Revert "Delete packages/desktop-client/e2e directory"
This reverts commit 2476e45735.
2024-06-08 11:55:09 -04:00
Crazypkr
ab124105c2 Revert "Delete packages/desktop-client/e2e directory"
This reverts commit bbec585305.
2024-06-08 11:53:54 -04:00
Crazypkr1099
129b2c3061 Create 2859.md 2024-06-08 11:39:30 -04:00
Crazypkr
9d6b574708 Add ability to scroll to current position in table when leaving budgetable 2024-06-08 11:34:57 -04:00
Crazypkr1099
bbec585305 Delete packages/desktop-client/e2e directory 2024-06-06 23:51:10 -04:00
Crazypkr1099
2476e45735 Delete packages/desktop-client/e2e directory 2024-06-06 23:45:55 -04:00
804 changed files with 22096 additions and 33026 deletions

View File

@@ -14,7 +14,6 @@ packages/desktop-client/**/node_modules/*
packages/desktop-client/node_modules/
packages/desktop-client/src/icons/**/*
packages/desktop-client/test-results/
packages/desktop-client/playwright-report/
packages/desktop-electron/client-build/
packages/desktop-electron/dist/

View File

@@ -1,3 +1,4 @@
/* eslint-disable rulesdir/typography */
const path = require('path');
const rulesDirPlugin = require('eslint-plugin-rulesdir');
@@ -33,23 +34,9 @@ const restrictedImportColors = [
];
module.exports = {
root: true,
env: {
browser: true,
commonjs: true,
es6: true,
jest: true,
node: true,
},
plugins: [
'prettier',
'import',
'rulesdir',
'@typescript-eslint',
'jsx-a11y',
'react-hooks',
],
plugins: ['prettier', 'import', 'rulesdir', '@typescript-eslint'],
extends: [
'react-app',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:prettier/recommended',
@@ -64,184 +51,6 @@ module.exports = {
vi: true,
},
rules: {
// http://eslint.org/docs/rules/
'array-callback-return': 'warn',
'default-case': ['warn', { commentPattern: '^no default$' }],
'dot-location': ['warn', 'property'],
eqeqeq: ['warn', 'smart'],
'new-parens': 'warn',
'no-array-constructor': 'warn',
'no-caller': 'warn',
'no-cond-assign': ['warn', 'except-parens'],
'no-const-assign': 'warn',
'no-control-regex': 'warn',
'no-delete-var': 'warn',
'no-dupe-args': 'warn',
'no-dupe-class-members': 'warn',
'no-dupe-keys': 'warn',
'no-duplicate-case': 'warn',
'no-empty-character-class': 'warn',
'no-empty-pattern': 'warn',
'no-eval': 'warn',
'no-ex-assign': 'warn',
'no-extend-native': 'warn',
'no-extra-bind': 'warn',
'no-extra-label': 'warn',
'no-fallthrough': 'warn',
'no-func-assign': 'warn',
'no-implied-eval': 'warn',
'no-invalid-regexp': 'warn',
'no-iterator': 'warn',
'no-label-var': 'warn',
'no-labels': ['warn', { allowLoop: true, allowSwitch: false }],
'no-lone-blocks': 'warn',
'no-mixed-operators': [
'warn',
{
groups: [
['&', '|', '^', '~', '<<', '>>', '>>>'],
['==', '!=', '===', '!==', '>', '>=', '<', '<='],
['&&', '||'],
['in', 'instanceof'],
],
allowSamePrecedence: false,
},
],
'no-multi-str': 'warn',
'no-global-assign': 'warn',
'no-unsafe-negation': 'warn',
'no-new-func': 'warn',
'no-new-object': 'warn',
'no-new-symbol': 'warn',
'no-new-wrappers': 'warn',
'no-obj-calls': 'warn',
'no-octal': 'warn',
'no-octal-escape': 'warn',
'no-redeclare': 'warn',
'no-regex-spaces': 'warn',
'no-script-url': 'warn',
'no-self-assign': 'warn',
'no-self-compare': 'warn',
'no-sequences': 'warn',
'no-shadow-restricted-names': 'warn',
'no-sparse-arrays': 'warn',
'no-template-curly-in-string': 'warn',
'no-this-before-super': 'warn',
'no-throw-literal': 'warn',
'no-undef': 'error',
'no-unreachable': 'warn',
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
'no-unused-labels': 'warn',
'no-use-before-define': [
'warn',
{
functions: false,
classes: false,
variables: false,
},
],
'no-useless-computed-key': 'warn',
'no-useless-concat': 'warn',
'no-useless-constructor': 'warn',
'no-useless-escape': 'warn',
'no-useless-rename': [
'warn',
{
ignoreDestructuring: false,
ignoreImport: false,
ignoreExport: false,
},
],
'no-with': 'warn',
'no-whitespace-before-property': 'warn',
'react-hooks/exhaustive-deps': 'warn',
'require-yield': 'warn',
'rest-spread-spacing': ['warn', 'never'],
strict: ['warn', 'never'],
'unicode-bom': ['warn', 'never'],
'use-isnan': 'warn',
'valid-typeof': 'warn',
'no-restricted-properties': [
'error',
{
object: 'require',
property: 'ensure',
message:
'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
},
{
object: 'System',
property: 'import',
message:
'Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting',
},
],
'getter-return': 'warn',
// https://github.com/benmosher/eslint-plugin-import/tree/master/docs/rules
'import/first': 'error',
'import/no-amd': 'error',
'import/no-anonymous-default-export': 'warn',
'import/no-webpack-loader-syntax': 'error',
// https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules
'react/forbid-foreign-prop-types': ['warn', { allowInPropTypes: true }],
'react/jsx-no-comment-textnodes': 'warn',
'react/jsx-no-duplicate-props': 'warn',
'react/jsx-no-target-blank': 'warn',
'react/jsx-no-undef': 'error',
'react/jsx-pascal-case': [
'warn',
{
allowAllCaps: true,
ignore: [],
},
],
'react/no-danger-with-children': 'warn',
// Disabled because of undesirable warnings
// See https://github.com/facebook/create-react-app/issues/5204 for
// blockers until its re-enabled
// 'react/no-deprecated': 'warn',
'react/no-direct-mutation-state': 'warn',
'react/no-is-mounted': 'warn',
'react/no-typos': 'error',
'react/require-render-return': 'error',
'react/style-prop-object': 'warn',
// https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules
'jsx-a11y/alt-text': 'warn',
'jsx-a11y/anchor-has-content': 'warn',
'jsx-a11y/anchor-is-valid': [
'warn',
{
aspects: ['noHref', 'invalidHref'],
},
],
'jsx-a11y/aria-activedescendant-has-tabindex': 'warn',
'jsx-a11y/aria-props': 'warn',
'jsx-a11y/aria-proptypes': 'warn',
'jsx-a11y/aria-role': ['warn', { ignoreNonDOM: true }],
'jsx-a11y/aria-unsupported-elements': 'warn',
'jsx-a11y/heading-has-content': 'warn',
'jsx-a11y/iframe-has-title': 'warn',
'jsx-a11y/img-redundant-alt': 'warn',
'jsx-a11y/no-access-key': 'warn',
'jsx-a11y/no-distracting-elements': 'warn',
'jsx-a11y/no-redundant-roles': 'warn',
'jsx-a11y/role-has-required-aria-props': 'warn',
'jsx-a11y/role-supports-aria-props': 'warn',
'jsx-a11y/scope': 'warn',
// https://github.com/facebook/react/tree/main/packages/eslint-plugin-react-hooks
'react-hooks/rules-of-hooks': 'error',
'prettier/prettier': 'warn',
// Note: base rule explicitly disabled in favor of the TS one
@@ -251,7 +60,6 @@ module.exports = {
{
varsIgnorePattern: '^(_|React)',
ignoreRestSiblings: true,
caughtErrors: 'none',
},
],
@@ -358,63 +166,9 @@ module.exports = {
'prefer-const': 'warn',
'prefer-spread': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-require-imports': 'off',
'import/no-default-export': 'warn',
},
overrides: [
{
files: ['**/*.ts?(x)'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
// typescript-eslint specific options
warnOnUnsupportedTypeScriptVersion: true,
},
plugins: ['@typescript-eslint'],
// If adding a typescript-eslint version of an existing ESLint rule,
// make sure to disable the ESLint rule here.
rules: {
// TypeScript's `noFallthroughCasesInSwitch` option is more robust (#6906)
'default-case': 'off',
// 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/291)
'no-dupe-class-members': 'off',
// 'tsc' already handles this (https://github.com/typescript-eslint/typescript-eslint/issues/477)
'no-undef': 'off',
// Add TypeScript specific rules (and turn off ESLint equivalents)
'@typescript-eslint/consistent-type-assertions': 'warn',
'no-array-constructor': 'off',
'@typescript-eslint/no-array-constructor': 'warn',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'warn',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': [
'warn',
{
functions: false,
classes: false,
variables: false,
typedefs: false,
},
],
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': 'warn',
},
},
{
files: ['.eslintrc.js', './**/.eslintrc.js'],
parserOptions: { project: null },
@@ -435,7 +189,7 @@ module.exports = {
'warn',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
],
'@typescript-eslint/no-restricted-types': [
'@typescript-eslint/ban-types': [
'warn',
{
types: {
@@ -443,6 +197,7 @@ module.exports = {
FunctionComponent: { message: ruleFCMsg },
FC: { message: ruleFCMsg },
},
extendDefaults: true,
},
],
},
@@ -526,8 +281,8 @@ module.exports = {
'./packages/desktop-client/src/components/budget/DynamicBudgetTable.tsx',
'./packages/desktop-client/src/components/budget/index.tsx',
'./packages/desktop-client/src/components/budget/MobileBudget.tsx',
'./packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx',
'./packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx',
'./packages/desktop-client/src/components/budget/rollover/HoldMenu.tsx',
'./packages/desktop-client/src/components/budget/rollover/TransferMenu.tsx',
'./packages/desktop-client/src/components/common/Menu.tsx',
'./packages/desktop-client/src/components/FinancesApp.tsx',
'./packages/desktop-client/src/components/GlobalKeys.ts',
@@ -577,23 +332,8 @@ module.exports = {
'react-hooks/exhaustive-deps': 'off',
},
},
{
files: [
'.eslintrc.js',
'*.test.js',
'*.test.ts',
'*.test.jsx',
'*.test.tsx',
],
rules: {
'rulesdir/typography': 'off',
},
},
],
settings: {
react: {
version: 'detect',
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,

View File

@@ -40,7 +40,6 @@ jobs:
python3 -m pip install setuptools
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
run: |
sudo apt-get update
sudo apt-get install flatpak -y
sudo apt-get install flatpak-builder -y
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
@@ -49,17 +48,13 @@ jobs:
sudo flatpak install org.electronjs.Electron2.BaseApp/x86_64/23.08 -y
- name: Set up environment
uses: ./.github/actions/setup
- name: Build Electron for Mac
if: ${{ startsWith(matrix.os, 'macos') }}
- name: Build Electron
run: ./bin/package-electron
env:
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
# CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
# CSC_LINK: ${{ secrets.CSC_LINK }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- name: Build Electron
if: ${{ ! startsWith(matrix.os, 'macos') }}
run: ./bin/package-electron
- name: Upload Build
uses: actions/upload-artifact@v4
with:
@@ -67,22 +62,13 @@ jobs:
path: |
packages/desktop-electron/dist/*.dmg
packages/desktop-electron/dist/*.exe
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@v4
with:
name: actual-electron-${{ matrix.os }}-appx
path: |
packages/desktop-electron/dist/*.appx
- name: Add to Release
uses: softprops/action-gh-release@v2
with:
files: |
packages/desktop-electron/dist/*.dmg
packages/desktop-electron/dist/*.exe
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak

View File

@@ -35,7 +35,6 @@ jobs:
python3 -m pip install setuptools
- if: ${{ startsWith(matrix.os, 'ubuntu') }}
run: |
sudo apt-get update
sudo apt-get install flatpak -y
sudo apt-get install flatpak-builder -y
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
@@ -53,13 +52,5 @@ jobs:
path: |
packages/desktop-electron/dist/*.dmg
packages/desktop-electron/dist/*.exe
!packages/desktop-electron/dist/Actual-windows.exe
packages/desktop-electron/dist/*.AppImage
packages/desktop-electron/dist/*.flatpak
- name: Upload Windows Store Build
if: ${{ startsWith(matrix.os, 'windows') }}
uses: actions/upload-artifact@v4
with:
name: actual-electron-${{ matrix.os }}-appx
path: |
packages/desktop-electron/dist/*.appx

View File

@@ -7,20 +7,10 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v8
with:
stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
close-pr-message: 'This PR was closed because it has been stalled for 5 days with no activity.'
days-before-stale: 30
days-before-close: 5
days-before-issue-stale: -1
stale-wip:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
stale-pr-message: ':wave: Hi! It looks like this PR has not had any changes for a week now. Would you like someone to review this PR? If so - please remove the "[WIP]" prefix from the PR title. That will let the community know that this PR is open for a review.'
days-before-stale: 7
any-of-labels: ':construction: WIP'
days-before-close: -1
days-before-issue-stale: -1

39
.github/workflows/trafico.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
##########################################################################################
# WARNING! This workflow uses the 'pull_request_target' event. That mans that it will #
# always run in the context of the main actualbudget/actual repo, even if the PR is from #
# a fork. This is necessary to get access to a GitHub token that can modify the PR. #
# Be VERY CAREFUL about adding things to this workflow, since forks can inject #
# arbitrary code into their branch, and can pollute the artifacts we download. Arbitrary #
# code execution in this workflow could lead to a compromise of the main repo. #
##########################################################################################
# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests #
##########################################################################################
name: Trafico Reviews
on:
pull_request_target:
types:
- opened
- closed
- reopened
- synchronize
- edited
- review_requested
- review_request_removed
pull_request_review:
types: [submitted, edited, dismissed]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
manage-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actualbudget/trafico@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

27
.github/workflows/wip.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Add WIP
on:
pull_request_target:
types:
- opened
jobs:
add_wip_prefix:
if: |
join(github.event.pull_request.requested_reviewers) == ''
&& !contains(github.event.pull_request.title, 'WIP')
&& !contains(github.event.pull_request.labels.*.name, 'WIP')
&& github.event.pull_request.draft != true
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Add WIP
env:
TITLE: ${{ github.event.pull_request.title }}
shell: bash
run: |
echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token
gh pr edit ${{ github.event.pull_request.number }} -t "[WIP] ${TITLE}"

1
.gitignore vendored
View File

@@ -21,7 +21,6 @@ packages/api/dist
packages/api/@types
packages/crdt/dist
packages/desktop-electron/client-build
packages/desktop-electron/build
packages/desktop-electron/.electron-symbols
packages/desktop-electron/dist
packages/desktop-electron/loot-core

View File

@@ -1 +0,0 @@
yarn lint-staged

893
.yarn/releases/yarn-4.0.2.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,4 +4,4 @@ enableGlobalCache: false
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.3.1.cjs
yarnPath: .yarn/releases/yarn-4.0.2.cjs

View File

@@ -14,40 +14,22 @@ Want to say thanks? Click the ⭐ at the top of the page.
- Actual [discord](https://discord.gg/pRYNYr4W5A) community.
- Actual [Community Documentation](https://actualbudget.org/docs)
- [Frequently asked questions](https://actualbudget.org/docs/faq)
## Installation
There are four ways to deploy Actual:
If you are only interested in running the latest version and not contributing to the source code, you don't need to clone this repo. You can get the latest version through npm.
1. One-click deployment [via PikaPods](https://www.pikapods.com/pods?run=actual) (~1.40 $/month) - recommended for non-technical users
1. Managed hosting [via Fly.io](https://actualbudget.org/docs/install/fly) (~1.50 $/month)
1. Self-hosted by using [a Docker image](https://actualbudget.org/docs/install/docker)
1. Local-only apps - [downloadable Windows, Mac and Linux apps](https://actualbudget.org/download/) you can run on your device
### The easy way: using a server (recommended)
Learn more in the [installation instructions docs](https://actualbudget.org/docs/install/).
The easiest way to get Actual running is to use the [actual-server](https://github.com/actualbudget/actual-server) project. That is the server for syncing changes across devices, and it comes with the latest version of Actual. The server will provide both the web project and a server for syncing.
## Ready to Start Budgeting?
Read about [Envelope budgeting](https://actualbudget.org/docs/getting-started/envelope-budgeting) to know more about the idea behind Actual Budget.
### Are you new to budgeting or want to start fresh?
Check out the community's [Starting Fresh](https://actualbudget.org/docs/getting-started/starting-fresh) guide so you can quickly get up and running!
### Are you migrating from other budgeting apps?
Check out the community's [Migration](https://actualbudget.org/docs/migration/) guide to start jumping on the Actual Budget train!
You can get up and running quickly and easily by following our [Running Actual Locally Guide](https://actualbudget.org/docs/install/local)
## Documentation
We have a wide range of documentation on how to use Actual, this is all available in our [Community Documentation](https://actualbudget.org/docs), this includes topics on Budgeting, Account Management, Tips & Tricks and some documentation for developers.
## Contributing
Actual is a community driven product. Learn more about [contributing to Actual](https://actualbudget.org/docs/contributing/).
### Code structure
## Code structure
The Actual app is split up into a few packages:
@@ -57,23 +39,15 @@ The Actual app is split up into a few packages:
More information on the project structure is available in our [community documentation](https://actualbudget.org/docs/contributing/project-details).
### Feature Requests
## Feature Requests
Current feature requests can be seen [here](https://github.com/actualbudget/actual/issues?q=is%3Aissue+label%3A%22needs+votes%22+sort%3Areactions-%2B1-desc).
Vote for your favorite requests by reacting :+1: to the top comment of the request.
To add new feature requests, open a new Issue of the "Feature Request" type.
### Translation
Make Actual Budget accessible to more people by helping with the [Internationalization](https://actualbudget.org/docs/contributing/i18n/) of Actual. We are using a crowd sourcing tool to manage the translations, see our [Weblate Project](https://hosted.weblate.org/projects/actualbudget/). Weblate proudly supports open-source software projects through their [Libre plan](https://weblate.org/en/hosting/#libre).
## Repo Activity
![Alt](https://repobeats.axiom.co/api/embed/e20537dd8b74956f86736726ccfbc6f0565bec22.svg 'Repobeats analytics image')
## Sponsors
Thanks to our wonderful sponsors who make Actual Budget possible!
Thanks to our wonderful sponsors who make Actual budget possible!
<a href="https://www.netlify.com"> <img src="https://www.netlify.com/v3/img/components/netlify-color-accent.svg" alt="Deploys by Netlify" /> </a>

View File

@@ -34,6 +34,8 @@ if [ "$OSTYPE" == "msys" ]; then
fi
fi
yarn rebuild-electron
yarn workspace loot-core build:node
yarn workspace @actual-app/web build --mode=desktop
@@ -48,10 +50,10 @@ yarn workspace desktop-electron update-client
if [ -f ../../.secret-tokens ]; then
source ../../.secret-tokens
fi
yarn build
yarn build --publish never --arm64 --x64
echo "\nCreated release"
else
SKIP_NOTARIZATION=true yarn build
SKIP_NOTARIZATION=true yarn build --publish never --x64
fi
)

View File

@@ -28,5 +28,5 @@ echo "Running VRT tests with the following parameters:"
echo "E2E_START_URL: $E2E_START_URL"
echo "VRT_ARGS: $VRT_ARGS"
MSYS_NO_PATHCONV=1 docker run --rm --network host -v "$(pwd)":/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash \
docker run --rm --network host -v "$(pwd)":/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash \
-c "E2E_START_URL=$E2E_START_URL yarn vrt $VRT_ARGS"

View File

@@ -30,7 +30,6 @@
"build:browser": "./bin/package-browser",
"build:desktop": "./bin/package-electron",
"build:api": "yarn workspace @actual-app/api build",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"test": "yarn workspaces foreach --all --parallel --verbose run test",
"test:debug": "yarn workspaces foreach --all --verbose run test",
"e2e": "yarn workspaces foreach --all --parallel --verbose run e2e",
@@ -41,31 +40,24 @@
"lint": "eslint . --max-warnings 0 --ext .js,.jsx,.ts,.tsx",
"lint:verbose": "DEBUG=eslint:cli-engine eslint . --max-warnings 0",
"typecheck": "yarn tsc && tsc-strict",
"jq": "./node_modules/node-jq/bin/jq",
"prepare": "husky"
"jq": "./node_modules/node-jq/bin/jq"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"confusing-browser-globals": "^1.0.11",
"cross-env": "^7.0.3",
"eslint": "^8.57.0",
"eslint": "^8.37.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-config-react-app": "7.0.1",
"eslint-import-resolver-typescript": "3.5.5",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "5.1.3",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-rulesdir": "^0.2.2",
"husky": "^9.0.11",
"lint-staged": "^15.2.9",
"node-jq": "^4.0.1",
"npm-run-all": "^4.1.5",
"prettier": "3.3.3",
"prettier": "3.2.4",
"source-map-support": "^0.5.21",
"typescript": "^5.5.4",
"typescript-strict-plugin": "^2.4.4"
"typescript": "^5.0.2",
"typescript-strict-plugin": "^2.2.2-beta.2"
},
"resolutions": {
"rollup": "4.9.4"
@@ -73,10 +65,7 @@
"engines": {
"node": ">=18.0.0"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,md,json}": "prettier --write"
},
"packageManager": "yarn@4.3.1",
"packageManager": "yarn@4.0.2",
"browserslist": [
"electron 24.0",
"defaults"

View File

@@ -58,19 +58,6 @@ describe('API CRUD operations', () => {
await api.loadBudget(budgetName);
});
// api: getBudgets
test('getBudgets', async () => {
const budgets = await api.getBudgets();
expect(budgets).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: 'test-budget',
name: 'Default Test Db',
}),
]),
);
});
// apis: getCategoryGroups, createCategoryGroup, updateCategoryGroup, deleteCategoryGroup
test('CategoryGroups: successfully update category groups', async () => {
const month = '2023-10';
@@ -81,22 +68,28 @@ describe('API CRUD operations', () => {
expect(groups).toEqual(
expect.arrayContaining([
expect.objectContaining({
hidden: false,
hidden: 0,
id: 'fc3825fd-b982-4b72-b768-5b30844cf832',
is_income: false,
is_income: 0,
name: 'Usual Expenses',
sort_order: 16384,
tombstone: 0,
}),
expect.objectContaining({
hidden: false,
hidden: 0,
id: 'a137772f-cf2f-4089-9432-822d2ddc1466',
is_income: false,
is_income: 0,
name: 'Investments and Savings',
sort_order: 32768,
tombstone: 0,
}),
expect.objectContaining({
hidden: false,
hidden: 0,
id: '2E1F5BDB-209B-43F9-AF2C-3CE28E380C00',
is_income: true,
is_income: 1,
name: 'Income',
sort_order: 32768,
tombstone: 0,
}),
]),
);
@@ -258,7 +251,7 @@ describe('API CRUD operations', () => {
);
});
//apis: createAccount, getAccounts, updateAccount, closeAccount, deleteAccount, reopenAccount, getAccountBalance
//apis: createAccount, getAccounts, updateAccount, closeAccount, deleteAccount, reopenAccount
test('Accounts: successfully complete account operators', async () => {
const accountId1 = await api.createAccount(
{ name: 'test-account1', offbudget: true },
@@ -279,9 +272,6 @@ describe('API CRUD operations', () => {
]),
);
expect(await api.getAccountBalance(accountId1)).toEqual(1000);
expect(await api.getAccountBalance(accountId2)).toEqual(0);
await api.updateAccount(accountId1, { offbudget: false });
await api.closeAccount(accountId1, accountId2, null);
await api.deleteAccount(accountId2);
@@ -557,10 +547,10 @@ describe('API CRUD operations', () => {
);
// delete rules
await api.deleteRule(rules[1].id);
await api.deleteRule(rules[1]);
expect(await api.getRules()).toHaveLength(1);
await api.deleteRule(rules[0].id);
await api.deleteRule(rules[0]);
expect(await api.getRules()).toHaveLength(0);
});
@@ -579,11 +569,6 @@ describe('API CRUD operations', () => {
});
expect(addResult).toBe('ok');
expect(await api.getAccountBalance(accountId)).toEqual(200);
expect(
await api.getAccountBalance(accountId, new Date(2023, 10, 2)),
).toEqual(0);
// confirm added transactions exist
let transactions = await api.getTransactions(
accountId,

View File

@@ -31,10 +31,6 @@ export async function downloadBudget(syncId, { password }: { password? } = {}) {
return send('api/download-budget', { syncId, password });
}
export async function getBudgets() {
return send('api/get-budgets');
}
export async function sync() {
return send('api/sync');
}
@@ -129,10 +125,6 @@ export function deleteAccount(id) {
return send('api/account-delete', { id });
}
export function getAccountBalance(id, cutoff?) {
return send('api/account-balance', { id, cutoff });
}
export function getCategoryGroups() {
return send('api/category-groups-get');
}
@@ -165,10 +157,6 @@ export function deleteCategory(id, transferCategoryId?) {
return send('api/category-delete', { id, transferCategoryId });
}
export function getCommonPayees() {
return send('api/common-payees-get');
}
export function getPayees() {
return send('api/payees-get');
}
@@ -185,10 +173,6 @@ export function deletePayee(id) {
return send('api/payee-delete', { id });
}
export function mergePayees(targetId, mergeIds) {
return send('api/payees-merge', { targetId, mergeIds });
}
export function getRules() {
return send('api/rules-get');
}
@@ -205,14 +189,6 @@ export function updateRule(rule) {
return send('api/rule-update', { rule });
}
export function deleteRule(id: string) {
return send('api/rule-delete', id);
}
export function holdBudgetForNextMonth(month, amount) {
return send('api/budget-hold-for-next-month', { month, amount });
}
export function resetBudgetHold(month) {
return send('api/budget-reset-hold', { month });
export function deleteRule(id) {
return send('api/rule-delete', { id });
}

View File

@@ -1,6 +1,6 @@
{
"name": "@actual-app/api",
"version": "24.10.0",
"version": "6.8.1",
"license": "MIT",
"description": "An API for Actual",
"engines": {
@@ -35,6 +35,6 @@
"@types/uuid": "^9.0.2",
"jest": "^27.5.1",
"tsc-alias": "^1.8.8",
"typescript": "^5.5.4"
"typescript": "^5.0.2"
}
}

View File

@@ -26,6 +26,6 @@
"@types/uuid": "^9.0.2",
"jest": "^27.5.1",
"ts-protoc-gen": "^0.15.0",
"typescript": "^5.5.4"
"typescript": "^5.0.2"
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import * as merkle from './merkle';
import { Timestamp } from './timestamp';

View File

@@ -134,7 +134,6 @@ export function diff(trie1: TrieNode, trie2: TrieNode): number | null {
node2 = node2[diffkey] || emptyTrie();
}
// eslint-disable-next-line no-unreachable
return null;
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Timestamp } from './timestamp';
describe('Timestamp', function () {

View File

@@ -154,7 +154,7 @@ export class Timestamp {
/**
* maximum timestamp
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
static max = Timestamp.parse(
'9999-12-31T23:59:59.999Z-FFFF-FFFFFFFFFFFFFFFF',
)!;
@@ -294,7 +294,7 @@ export class Timestamp {
/**
* zero/minimum timestamp
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
static zero = Timestamp.parse(
'1970-01-01T00:00:00.000Z-0000-0000000000000000',
)!;

View File

@@ -6,7 +6,6 @@ node_modules
# testing
coverage
test-results
playwright-report
# production
build

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -93,9 +93,7 @@ test.describe('Onboarding', () => {
test('navigates back to start page by clicking on “no server” in an empty budget file', async () => {
await configurationPage.clickOnNoServer();
const accountPage = await configurationPage.startFresh();
await expect(accountPage.transactionTable).toBeVisible();
await configurationPage.startFresh();
await navigation.clickOnNoServer();
await page.getByRole('button', { name: 'Start using a server' }).click();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 KiB

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 638 KiB

After

Width:  |  Height:  |  Size: 639 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 KiB

After

Width:  |  Height:  |  Size: 635 KiB

View File

@@ -16,8 +16,8 @@ export class AccountPage {
this.cancelTransactionButton = this.page.getByRole('button', {
name: 'Cancel',
});
this.accountMenuButton = this.page.getByRole('button', {
name: 'Account menu',
this.menuButton = this.page.getByRole('button', {
name: 'Menu',
});
this.transactionTable = this.page.getByTestId('transaction-table');
@@ -30,28 +30,16 @@ export class AccountPage {
this.selectTooltip = this.page.getByTestId('transactions-select-tooltip');
}
/**
* Enter details of a transaction
*/
async enterSingleTransaction(transaction) {
await this.addNewTransactionButton.click();
await this._fillTransactionFields(this.newTransactionRow, transaction);
}
/**
* Finish adding a transaction
*/
async addEnteredTransaction() {
await this.addTransactionButton.click();
await this.cancelTransactionButton.click();
}
/**
* Create a single transaction
*/
async createSingleTransaction(transaction) {
await this.enterSingleTransaction(transaction);
await this.addEnteredTransaction();
await this.addNewTransactionButton.click();
await this._fillTransactionFields(this.newTransactionRow, transaction);
await this.addTransactionButton.click();
await this.cancelTransactionButton.click();
}
/**
@@ -94,15 +82,6 @@ export class AccountPage {
*/
getNthTransaction(index) {
const row = this.transactionTableRow.nth(index);
return this._getTransactionDetails(row);
}
getEnteredTransaction() {
return this._getTransactionDetails(this.newTransactionRow);
}
_getTransactionDetails(row) {
const account = row.getByTestId('account');
return {
@@ -124,10 +103,10 @@ export class AccountPage {
* Open the modal for closing the account.
*/
async clickCloseAccount() {
await this.accountMenuButton.click();
await this.menuButton.click();
await this.page.getByRole('button', { name: 'Close Account' }).click();
return new CloseAccountModal(
this.page.getByTestId('close-account-modal'),
this.page.locator('css=[aria-modal]'),
this.page,
);
}

View File

@@ -1,4 +1,3 @@
import { AccountPage } from './account-page';
import { BudgetPage } from './budget-page';
export class ConfigurationPage {
@@ -19,8 +18,6 @@ export class ConfigurationPage {
async startFresh() {
await this.page.getByRole('button', { name: 'Start fresh' }).click();
return new AccountPage(this.page);
}
async importBudget(type, file) {

View File

@@ -1,33 +0,0 @@
export class CustomReportPage {
constructor(page) {
this.page = page;
this.pageContent = page.getByTestId('reports-page');
this.showLegendButton = this.pageContent.getByRole('button', {
name: 'Show Legend',
});
this.showSummaryButton = this.pageContent.getByRole('button', {
name: 'Show Summary',
});
this.showLabelsButton = this.pageContent.getByRole('button', {
name: 'Show Labels',
});
}
async selectViz(vizName) {
await this.pageContent.getByRole('button', { name: vizName }).click();
}
async selectMode(mode) {
switch (mode) {
case 'total':
await this.pageContent.getByRole('button', { name: 'Total' }).click();
break;
case 'time':
await this.pageContent.getByRole('button', { name: 'Time' }).click();
break;
default:
throw new Error(`Unrecognized mode: ${mode}`);
}
}
}

View File

@@ -23,7 +23,7 @@ export class MobileAccountsPage {
* Click on the n-th account to open it up
*/
async openNthAccount(idx) {
await this.accounts.nth(idx).click();
await this.accounts.nth(idx).getByRole('button').click();
return new MobileAccountPage(this.page);
}

View File

@@ -1,5 +1,3 @@
import { CustomReportPage } from './custom-report-page';
export class ReportsPage {
constructor(page) {
this.page = page;
@@ -7,29 +5,22 @@ export class ReportsPage {
}
async waitToLoad() {
return this.pageContent.getByRole('button', { name: /^Net/ }).waitFor();
return this.pageContent.getByRole('link', { name: /^Net/ }).waitFor();
}
async goToNetWorthPage() {
await this.pageContent.getByRole('button', { name: /^Net/ }).click();
await this.pageContent.getByRole('link', { name: /^Net/ }).click();
return new ReportsPage(this.page);
}
async goToCashFlowPage() {
await this.pageContent.getByRole('button', { name: /^Cash/ }).click();
await this.pageContent.getByRole('link', { name: /^Cash/ }).click();
return new ReportsPage(this.page);
}
async goToCustomReportPage() {
await this.pageContent
.getByRole('button', { name: 'Create new custom report' })
.click();
return new CustomReportPage(this.page);
}
async getAvailableReportList() {
return this.pageContent
.getByRole('button')
.getByRole('link')
.getByRole('heading')
.allTextContents();
}

View File

@@ -44,7 +44,7 @@ export class RulesPage {
.first()
.click();
await this.page
.getByRole('button', { exact: true, name: data.conditionsOp })
.getByRole('option', { exact: true, name: data.conditionsOp })
.click();
}
@@ -52,7 +52,6 @@ export class RulesPage {
await this._fillEditorFields(
data.conditions,
this.page.getByTestId('condition-list'),
true,
);
}
@@ -64,19 +63,28 @@ export class RulesPage {
}
if (data.splits) {
let idx = data.actions?.length ?? 0;
for (const splitActions of data.splits) {
await this.page.getByTestId('add-split-transactions').click();
if (data.splits.beforeSplitActions) {
await this._fillEditorFields(
splitActions,
this.page.getByTestId('action-list').nth(idx),
data.splits.beforeSplitActions,
this.page.getByTestId('action-list'),
);
idx++;
}
if (data.splits.splitActions) {
let idx = data.splits?.beforeSplitActions.length ?? 0;
for (const splitActions of data.splits.splitActions) {
await this.page.getByTestId('add-split-transactions').click();
await this._fillEditorFields(
splitActions,
this.page.getByTestId('action-list').nth(idx),
);
idx++;
}
}
}
}
async _fillEditorFields(data, rootElement, fieldFirst = false) {
async _fillEditorFields(data, rootElement) {
for (const idx in data) {
const { field, op, value } = data[idx];
@@ -86,25 +94,16 @@ export class RulesPage {
await rootElement.getByRole('button', { name: 'Add entry' }).click();
}
if (op && !fieldFirst) {
await row.getByTestId('op-select').getByRole('button').first().click();
await this.page.getByRole('button', { name: op, exact: true }).click();
}
if (field) {
await row
.getByTestId('field-select')
.getByRole('button')
.first()
.click();
await row.getByRole('button').first().click();
await this.page
.getByRole('button', { name: field, exact: true })
.getByRole('option', { exact: true, name: field })
.click();
}
if (op && fieldFirst) {
await row.getByTestId('op-select').getByRole('button').first().click();
await this.page.getByRole('button', { name: op, exact: true }).click();
if (op) {
await row.getByRole('button', { name: 'is' }).click();
await this.page.getByRole('option', { name: op, exact: true }).click();
}
if (value) {

View File

@@ -84,10 +84,6 @@ export class SchedulesPage {
if (data.amount) {
await this.page.getByLabel('Amount').fill(String(data.amount));
// For some readon, the input field does not trigger the change event on tests
// but it works on the browser. We can revisit this once migration to
// react aria components is complete.
await this.page.keyboard.press('Enter');
}
}
}

View File

@@ -3,7 +3,7 @@ import { test, expect } from '@playwright/test';
import { ConfigurationPage } from './page-models/configuration-page';
import { Navigation } from './page-models/navigation';
test.describe.parallel('Reports', () => {
test.describe('Reports', () => {
let page;
let navigation;
let reportsPage;
@@ -43,66 +43,4 @@ test.describe.parallel('Reports', () => {
await reportsPage.goToCashFlowPage();
await expect(page).toMatchThemeScreenshots();
});
test.describe.parallel('custom reports', () => {
let customReportPage;
test.beforeEach(async () => {
customReportPage = await reportsPage.goToCustomReportPage();
});
test('Switches to Data Table and checks the visuals', async () => {
await customReportPage.selectMode('time');
await customReportPage.selectViz('Data Table');
await expect(page).toMatchThemeScreenshots();
});
test('Switches to Bar Graph and checks the visuals', async () => {
await customReportPage.selectMode('time');
await customReportPage.selectViz('Bar Graph');
await expect(page).toMatchThemeScreenshots();
});
test('Switches to Line Graph and checks the visuals', async () => {
await customReportPage.selectMode('time');
await customReportPage.selectViz('Line Graph');
await expect(page).toMatchThemeScreenshots();
});
test('Switches to Area Graph and checks the visuals', async () => {
await customReportPage.selectMode('total');
await customReportPage.selectViz('Area Graph');
await expect(page).toMatchThemeScreenshots();
});
test('Switches to Donut Graph and checks the visuals', async () => {
await customReportPage.selectMode('total');
await customReportPage.selectViz('Donut Graph');
await expect(page).toMatchThemeScreenshots();
});
test('Validates that "show legend" button shows the legend side-bar', async () => {
await customReportPage.selectViz('Bar Graph');
await customReportPage.showLegendButton.click();
await expect(page).toMatchThemeScreenshots();
await customReportPage.showLegendButton.click();
});
test('Validates that "show summary" button shows the summary', async () => {
await customReportPage.selectViz('Bar Graph');
await customReportPage.showSummaryButton.click();
await expect(page).toMatchThemeScreenshots();
await customReportPage.showSummaryButton.click();
});
test('Validates that "show labels" button shows the labels', async () => {
await customReportPage.selectViz('Bar Graph');
await customReportPage.showLabelsButton.click();
await expect(page).toMatchThemeScreenshots();
await customReportPage.showLabelsButton.click();
});
});
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -32,7 +32,6 @@ test.describe('Rules', () => {
});
test('creates a rule and makes sure it is applied when creating a transaction', async () => {
await rulesPage.searchFor('Fast Internet');
await rulesPage.createRule({
conditions: [
{
@@ -49,6 +48,7 @@ test.describe('Rules', () => {
],
});
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']);
@@ -79,34 +79,35 @@ test.describe('Rules', () => {
value: 'Ikea',
},
],
actions: [
{
op: 'set',
field: 'notes',
value: 'food / entertainment',
},
],
splits: [
[
splits: {
beforeSplitActions: [
{
field: 'a fixed percent of the remainder',
value: '90',
},
{
field: 'category',
value: 'Entertainment',
field: 'notes',
value: 'food / entertainment',
},
],
[
{
field: 'an equal portion of the remainder',
},
{
field: 'category',
value: 'Food',
},
splitActions: [
[
{
field: 'a fixed percent',
value: '90',
},
{
field: 'category',
value: 'Entertainment',
},
],
[
{
field: 'an equal portion of the remainder',
},
{
field: 'category',
value: 'Food',
},
],
],
],
},
});
const accountPage = await navigation.goToAccountPage(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Some files were not shown because too many files have changed in this diff Show More