Compare commits
225 Commits
react-aria
...
v24.10.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b7a79a01c | ||
|
|
125510c981 | ||
|
|
327887b87d | ||
|
|
47ef916873 | ||
|
|
5064b06f2c | ||
|
|
4df03984bd | ||
|
|
92980ab55b | ||
|
|
3b97d1eec7 | ||
|
|
545c8d5456 | ||
|
|
f79edf866a | ||
|
|
83ea40dff9 | ||
|
|
444ac83697 | ||
|
|
8f725c7911 | ||
|
|
6725d56bb8 | ||
|
|
666b7870b7 | ||
|
|
686ce5b504 | ||
|
|
4373f4d8f9 | ||
|
|
479572fadb | ||
|
|
6e627c4e2e | ||
|
|
0f41e95952 | ||
|
|
7e889300ef | ||
|
|
c497d3a941 | ||
|
|
fe17c6ba75 | ||
|
|
3a9a929f56 | ||
|
|
88a7432975 | ||
|
|
373dfb0465 | ||
|
|
80a7a9873a | ||
|
|
9c2bb9b3de | ||
|
|
2acf996430 | ||
|
|
f3451bfc2e | ||
|
|
48cdffbc03 | ||
|
|
37d201b6fb | ||
|
|
d1ecb3db44 | ||
|
|
90e2fe60d1 | ||
|
|
09e3721036 | ||
|
|
6354598d48 | ||
|
|
55df377a20 | ||
|
|
634508a3bc | ||
|
|
ec55e8dc9a | ||
|
|
a1bc66b10a | ||
|
|
4485a631cd | ||
|
|
25a4041958 | ||
|
|
e6bf6da381 | ||
|
|
e507b8ff43 | ||
|
|
5e12d4013a | ||
|
|
84af8b76be | ||
|
|
b3669b3001 | ||
|
|
6f41b20caf | ||
|
|
37d391b4fc | ||
|
|
7702ee4f4f | ||
|
|
d0ba623cfa | ||
|
|
ea675f11ee | ||
|
|
17fd06894a | ||
|
|
4e6a3bbace | ||
|
|
3743a328e3 | ||
|
|
6c87d85920 | ||
|
|
5b685ecc64 | ||
|
|
ae01066fe2 | ||
|
|
fefd1be22c | ||
|
|
bdbf6e9ca6 | ||
|
|
c5193b6d43 | ||
|
|
183c4b25a9 | ||
|
|
933804e836 | ||
|
|
0a59f793bf | ||
|
|
cfa9ac09d7 | ||
|
|
420aad0878 | ||
|
|
16944a6140 | ||
|
|
b2d7b65ce9 | ||
|
|
8c8c248ef7 | ||
|
|
6e8cdb30e8 | ||
|
|
3985d2549e | ||
|
|
68a2af0248 | ||
|
|
7231959f81 | ||
|
|
8498d7f788 | ||
|
|
d752389710 | ||
|
|
95ed7aaf27 | ||
|
|
21dc573f3f | ||
|
|
cb0411b180 | ||
|
|
62dbe3acf5 | ||
|
|
bbff543768 | ||
|
|
008a8a78b9 | ||
|
|
c466189007 | ||
|
|
b856c4874e | ||
|
|
407e3143eb | ||
|
|
ac90eb21a6 | ||
|
|
61bffa3d31 | ||
|
|
fca1bccda3 | ||
|
|
8e6fb4c64f | ||
|
|
5229fe7d16 | ||
|
|
bc04a8cbec | ||
|
|
0a34ede61a | ||
|
|
8a4a9ba083 | ||
|
|
61f5dcfd02 | ||
|
|
5cfa2cf577 | ||
|
|
3f8963273b | ||
|
|
1aa65946c2 | ||
|
|
44375e72ad | ||
|
|
6454c10e63 | ||
|
|
2a9546ced1 | ||
|
|
8926ff69b1 | ||
|
|
340169bfb6 | ||
|
|
3a905d3f9a | ||
|
|
7738ea0c00 | ||
|
|
8e077e0282 | ||
|
|
ae608f0cb8 | ||
|
|
f1c0d0b8a6 | ||
|
|
d9adb750d4 | ||
|
|
1750cd9081 | ||
|
|
7769d0303e | ||
|
|
9108b63355 | ||
|
|
1b70e59bde | ||
|
|
b48d256ec4 | ||
|
|
9c0e6a307b | ||
|
|
3e5ce72e27 | ||
|
|
b347f03fbb | ||
|
|
f3660c166f | ||
|
|
aaf96bbc2c | ||
|
|
6d84b0e371 | ||
|
|
db4b504e53 | ||
|
|
d6afc85a8c | ||
|
|
ee21155d1a | ||
|
|
65a7c58441 | ||
|
|
51ec600de2 | ||
|
|
af5fd5b3ef | ||
|
|
eccdc52342 | ||
|
|
4c192d7e1e | ||
|
|
f715ceafc9 | ||
|
|
af73dcd722 | ||
|
|
5e3485a8e2 | ||
|
|
1458dbc307 | ||
|
|
9ac77af077 | ||
|
|
3e07d18acd | ||
|
|
fa6cc26416 | ||
|
|
a1ca871b24 | ||
|
|
d9066a49c4 | ||
|
|
63ad6dadf2 | ||
|
|
89b096aa65 | ||
|
|
ee0156d35d | ||
|
|
9c17d55e0d | ||
|
|
411a6791b2 | ||
|
|
6f3af7b609 | ||
|
|
43ff1c033e | ||
|
|
09c44d351d | ||
|
|
a22160579d | ||
|
|
81df2ce7fd | ||
|
|
119d0b339d | ||
|
|
d1362c3d74 | ||
|
|
8142dd1ec9 | ||
|
|
2afd6967b4 | ||
|
|
fe922ec22e | ||
|
|
30a70f5627 | ||
|
|
65c5f2c559 | ||
|
|
1abca7619d | ||
|
|
6a85f84565 | ||
|
|
65329398fd | ||
|
|
a2e434a1fb | ||
|
|
d2bbe6a98e | ||
|
|
2c1967d788 | ||
|
|
798aee78c3 | ||
|
|
2807c98c2c | ||
|
|
5e9b976676 | ||
|
|
44ce976ffa | ||
|
|
5ba80fcbdc | ||
|
|
7b77f60458 | ||
|
|
81f59ff776 | ||
|
|
63d9547e7c | ||
|
|
d18fd36ae1 | ||
|
|
2b1ba88983 | ||
|
|
8be867f884 | ||
|
|
cafe480ba4 | ||
|
|
6472c70960 | ||
|
|
56c5a533e7 | ||
|
|
7e3ff1ad03 | ||
|
|
e0d7233b40 | ||
|
|
1b4c4319e1 | ||
|
|
14f29941b0 | ||
|
|
4389329bfa | ||
|
|
3a38c32b4c | ||
|
|
c3c6acd37c | ||
|
|
8de0f6a72a | ||
|
|
2799dbee3e | ||
|
|
58eeee825e | ||
|
|
6653dca776 | ||
|
|
77ba15f54c | ||
|
|
653a0ab104 | ||
|
|
2c26fa51a3 | ||
|
|
dff9911a15 | ||
|
|
3d5818f017 | ||
|
|
efd294dcef | ||
|
|
0eb62a09bc | ||
|
|
73d52fa0d0 | ||
|
|
5b0cc63f73 | ||
|
|
26a591f07f | ||
|
|
fe8851c797 | ||
|
|
511f677ae4 | ||
|
|
1cef0d11ee | ||
|
|
536cabb75b | ||
|
|
cceda03905 | ||
|
|
982f555a21 | ||
|
|
fe70ecb635 | ||
|
|
5c0bee6031 | ||
|
|
4439bb6abe | ||
|
|
b432204b4b | ||
|
|
9a85a72089 | ||
|
|
a970a78932 | ||
|
|
ed65805d53 | ||
|
|
88ae7e9375 | ||
|
|
0135a4d1b9 | ||
|
|
4af2c4f214 | ||
|
|
89a8f102dc | ||
|
|
d032fce7ea | ||
|
|
2fdc7fef32 | ||
|
|
1e41d695c5 | ||
|
|
12f91f7d86 | ||
|
|
f75d0f8099 | ||
|
|
07bbe00059 | ||
|
|
be0d363576 | ||
|
|
c2e648c9d5 | ||
|
|
33049a77e7 | ||
|
|
89241623f3 | ||
|
|
8434e8f5ce | ||
|
|
9b99debacc | ||
|
|
a23ec33591 | ||
|
|
aaea04fc00 | ||
|
|
b4f0087eef |
@@ -14,6 +14,7 @@ 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/
|
||||
|
||||
274
.eslintrc.js
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable rulesdir/typography */
|
||||
const path = require('path');
|
||||
|
||||
const rulesDirPlugin = require('eslint-plugin-rulesdir');
|
||||
@@ -34,9 +33,23 @@ const restrictedImportColors = [
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
plugins: ['prettier', 'import', 'rulesdir', '@typescript-eslint'],
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
jest: true,
|
||||
node: true,
|
||||
},
|
||||
plugins: [
|
||||
'prettier',
|
||||
'import',
|
||||
'rulesdir',
|
||||
'@typescript-eslint',
|
||||
'jsx-a11y',
|
||||
'react-hooks',
|
||||
],
|
||||
extends: [
|
||||
'react-app',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:prettier/recommended',
|
||||
@@ -51,6 +64,184 @@ 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
|
||||
@@ -60,6 +251,7 @@ module.exports = {
|
||||
{
|
||||
varsIgnorePattern: '^(_|React)',
|
||||
ignoreRestSiblings: true,
|
||||
caughtErrors: 'none',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -166,9 +358,63 @@ 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 },
|
||||
@@ -189,7 +435,7 @@ module.exports = {
|
||||
'warn',
|
||||
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
|
||||
],
|
||||
'@typescript-eslint/ban-types': [
|
||||
'@typescript-eslint/no-restricted-types': [
|
||||
'warn',
|
||||
{
|
||||
types: {
|
||||
@@ -197,7 +443,6 @@ module.exports = {
|
||||
FunctionComponent: { message: ruleFCMsg },
|
||||
FC: { message: ruleFCMsg },
|
||||
},
|
||||
extendDefaults: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -281,8 +526,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/rollover/HoldMenu.tsx',
|
||||
'./packages/desktop-client/src/components/budget/rollover/TransferMenu.tsx',
|
||||
'./packages/desktop-client/src/components/budget/envelope/HoldMenu.tsx',
|
||||
'./packages/desktop-client/src/components/budget/envelope/TransferMenu.tsx',
|
||||
'./packages/desktop-client/src/components/common/Menu.tsx',
|
||||
'./packages/desktop-client/src/components/FinancesApp.tsx',
|
||||
'./packages/desktop-client/src/components/GlobalKeys.ts',
|
||||
@@ -332,8 +577,23 @@ 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,
|
||||
|
||||
20
.github/workflows/electron-master.yml
vendored
@@ -40,6 +40,7 @@ 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
|
||||
@@ -48,13 +49,17 @@ jobs:
|
||||
sudo flatpak install org.electronjs.Electron2.BaseApp/x86_64/23.08 -y
|
||||
- name: Set up environment
|
||||
uses: ./.github/actions/setup
|
||||
- name: Build Electron
|
||||
- name: Build Electron for Mac
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
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:
|
||||
@@ -62,13 +67,22 @@ 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
|
||||
|
||||
9
.github/workflows/electron-pr.yml
vendored
@@ -35,6 +35,7 @@ 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
|
||||
@@ -52,5 +53,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
|
||||
|
||||
12
.github/workflows/stale.yml
vendored
@@ -7,10 +7,20 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- uses: actions/stale@v9
|
||||
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
|
||||
|
||||
1
.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
yarn lint-staged
|
||||
893
.yarn/releases/yarn-4.0.2.cjs
vendored
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
@@ -4,4 +4,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||
|
||||
40
README.md
@@ -14,22 +14,40 @@ 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
|
||||
|
||||
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.
|
||||
There are four ways to deploy Actual:
|
||||
|
||||
### The easy way: using a server (recommended)
|
||||
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 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.
|
||||
Learn more in the [installation instructions docs](https://actualbudget.org/docs/install/).
|
||||
|
||||
You can get up and running quickly and easily by following our [Running Actual Locally Guide](https://actualbudget.org/docs/install/local)
|
||||
## 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!
|
||||
|
||||
## 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.
|
||||
|
||||
## Code structure
|
||||
## Contributing
|
||||
|
||||
Actual is a community driven product. Learn more about [contributing to Actual](https://actualbudget.org/docs/contributing/).
|
||||
|
||||
### Code structure
|
||||
|
||||
The Actual app is split up into a few packages:
|
||||
|
||||
@@ -39,15 +57,23 @@ 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
|
||||
|
||||

|
||||
|
||||
## 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>
|
||||
|
||||
@@ -34,8 +34,6 @@ if [ "$OSTYPE" == "msys" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
yarn rebuild-electron
|
||||
|
||||
yarn workspace loot-core build:node
|
||||
|
||||
yarn workspace @actual-app/web build --mode=desktop
|
||||
@@ -50,10 +48,10 @@ yarn workspace desktop-electron update-client
|
||||
if [ -f ../../.secret-tokens ]; then
|
||||
source ../../.secret-tokens
|
||||
fi
|
||||
yarn build --publish never --arm64 --x64
|
||||
yarn build
|
||||
|
||||
echo "\nCreated release"
|
||||
else
|
||||
SKIP_NOTARIZATION=true yarn build --publish never --x64
|
||||
SKIP_NOTARIZATION=true yarn build
|
||||
fi
|
||||
)
|
||||
|
||||
@@ -28,5 +28,5 @@ echo "Running VRT tests with the following parameters:"
|
||||
echo "E2E_START_URL: $E2E_START_URL"
|
||||
echo "VRT_ARGS: $VRT_ARGS"
|
||||
|
||||
docker run --rm --network host -v "$(pwd)":/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash \
|
||||
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 \
|
||||
-c "E2E_START_URL=$E2E_START_URL yarn vrt $VRT_ARGS"
|
||||
|
||||
33
package.json
@@ -30,6 +30,7 @@
|
||||
"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",
|
||||
@@ -40,24 +41,31 @@
|
||||
"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"
|
||||
"jq": "./node_modules/node-jq/bin/jq",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"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.37.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"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-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-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.2.4",
|
||||
"prettier": "3.3.3",
|
||||
"source-map-support": "^0.5.21",
|
||||
"typescript": "^5.0.2",
|
||||
"typescript-strict-plugin": "^2.2.2-beta.2"
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-strict-plugin": "^2.4.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"rollup": "4.9.4"
|
||||
@@ -65,7 +73,10 @@
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,md,json}": "prettier --write"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1",
|
||||
"browserslist": [
|
||||
"electron 24.0",
|
||||
"defaults"
|
||||
|
||||
@@ -81,28 +81,22 @@ describe('API CRUD operations', () => {
|
||||
expect(groups).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
hidden: 0,
|
||||
hidden: false,
|
||||
id: 'fc3825fd-b982-4b72-b768-5b30844cf832',
|
||||
is_income: 0,
|
||||
is_income: false,
|
||||
name: 'Usual Expenses',
|
||||
sort_order: 16384,
|
||||
tombstone: 0,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
hidden: 0,
|
||||
hidden: false,
|
||||
id: 'a137772f-cf2f-4089-9432-822d2ddc1466',
|
||||
is_income: 0,
|
||||
is_income: false,
|
||||
name: 'Investments and Savings',
|
||||
sort_order: 32768,
|
||||
tombstone: 0,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
hidden: 0,
|
||||
hidden: false,
|
||||
id: '2E1F5BDB-209B-43F9-AF2C-3CE28E380C00',
|
||||
is_income: 1,
|
||||
is_income: true,
|
||||
name: 'Income',
|
||||
sort_order: 32768,
|
||||
tombstone: 0,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
@@ -563,10 +557,10 @@ describe('API CRUD operations', () => {
|
||||
);
|
||||
|
||||
// delete rules
|
||||
await api.deleteRule(rules[1]);
|
||||
await api.deleteRule(rules[1].id);
|
||||
expect(await api.getRules()).toHaveLength(1);
|
||||
|
||||
await api.deleteRule(rules[0]);
|
||||
await api.deleteRule(rules[0].id);
|
||||
expect(await api.getRules()).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -165,6 +165,10 @@ 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');
|
||||
}
|
||||
@@ -201,6 +205,14 @@ export function updateRule(rule) {
|
||||
return send('api/rule-update', { rule });
|
||||
}
|
||||
|
||||
export function deleteRule(id) {
|
||||
return send('api/rule-delete', { id });
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actual-app/api",
|
||||
"version": "6.8.2",
|
||||
"version": "24.10.0",
|
||||
"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.0.2"
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@
|
||||
"@types/uuid": "^9.0.2",
|
||||
"jest": "^27.5.1",
|
||||
"ts-protoc-gen": "^0.15.0",
|
||||
"typescript": "^5.0.2"
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as merkle from './merkle';
|
||||
import { Timestamp } from './timestamp';
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ export function diff(trie1: TrieNode, trie2: TrieNode): number | null {
|
||||
node2 = node2[diffkey] || emptyTrie();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unreachable
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { Timestamp } from './timestamp';
|
||||
|
||||
describe('Timestamp', function () {
|
||||
|
||||
@@ -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',
|
||||
)!;
|
||||
|
||||
1
packages/desktop-client/.gitignore
vendored
@@ -6,6 +6,7 @@ node_modules
|
||||
# testing
|
||||
coverage
|
||||
test-results
|
||||
playwright-report
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -93,7 +93,9 @@ test.describe('Onboarding', () => {
|
||||
|
||||
test('navigates back to start page by clicking on “no server” in an empty budget file', async () => {
|
||||
await configurationPage.clickOnNoServer();
|
||||
await configurationPage.startFresh();
|
||||
const accountPage = await configurationPage.startFresh();
|
||||
|
||||
await expect(accountPage.transactionTable).toBeVisible();
|
||||
|
||||
await navigation.clickOnNoServer();
|
||||
await page.getByRole('button', { name: 'Start using a server' }).click();
|
||||
|
||||
|
Before Width: | Height: | Size: 503 KiB After Width: | Height: | Size: 494 KiB |
|
Before Width: | Height: | Size: 577 KiB After Width: | Height: | Size: 565 KiB |
|
Before Width: | Height: | Size: 560 KiB After Width: | Height: | Size: 564 KiB |
|
Before Width: | Height: | Size: 567 KiB After Width: | Height: | Size: 565 KiB |
|
Before Width: | Height: | Size: 639 KiB After Width: | Height: | Size: 638 KiB |
|
Before Width: | Height: | Size: 635 KiB After Width: | Height: | Size: 634 KiB |
@@ -16,8 +16,8 @@ export class AccountPage {
|
||||
this.cancelTransactionButton = this.page.getByRole('button', {
|
||||
name: 'Cancel',
|
||||
});
|
||||
this.menuButton = this.page.getByRole('button', {
|
||||
name: 'Menu',
|
||||
this.accountMenuButton = this.page.getByRole('button', {
|
||||
name: 'Account menu',
|
||||
});
|
||||
|
||||
this.transactionTable = this.page.getByTestId('transaction-table');
|
||||
@@ -30,16 +30,28 @@ 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.addNewTransactionButton.click();
|
||||
|
||||
await this._fillTransactionFields(this.newTransactionRow, transaction);
|
||||
|
||||
await this.addTransactionButton.click();
|
||||
await this.cancelTransactionButton.click();
|
||||
await this.enterSingleTransaction(transaction);
|
||||
await this.addEnteredTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +94,15 @@ 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 {
|
||||
@@ -103,10 +124,10 @@ export class AccountPage {
|
||||
* Open the modal for closing the account.
|
||||
*/
|
||||
async clickCloseAccount() {
|
||||
await this.menuButton.click();
|
||||
await this.accountMenuButton.click();
|
||||
await this.page.getByRole('button', { name: 'Close Account' }).click();
|
||||
return new CloseAccountModal(
|
||||
this.page.locator('css=[aria-modal]'),
|
||||
this.page.getByTestId('close-account-modal'),
|
||||
this.page,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AccountPage } from './account-page';
|
||||
import { BudgetPage } from './budget-page';
|
||||
|
||||
export class ConfigurationPage {
|
||||
@@ -18,6 +19,8 @@ export class ConfigurationPage {
|
||||
|
||||
async startFresh() {
|
||||
await this.page.getByRole('button', { name: 'Start fresh' }).click();
|
||||
|
||||
return new AccountPage(this.page);
|
||||
}
|
||||
|
||||
async importBudget(type, file) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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).getByRole('button').click();
|
||||
await this.accounts.nth(idx).click();
|
||||
|
||||
return new MobileAccountPage(this.page);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CustomReportPage } from './custom-report-page';
|
||||
|
||||
export class ReportsPage {
|
||||
constructor(page) {
|
||||
this.page = page;
|
||||
@@ -5,22 +7,29 @@ export class ReportsPage {
|
||||
}
|
||||
|
||||
async waitToLoad() {
|
||||
return this.pageContent.getByRole('link', { name: /^Net/ }).waitFor();
|
||||
return this.pageContent.getByRole('button', { name: /^Net/ }).waitFor();
|
||||
}
|
||||
|
||||
async goToNetWorthPage() {
|
||||
await this.pageContent.getByRole('link', { name: /^Net/ }).click();
|
||||
await this.pageContent.getByRole('button', { name: /^Net/ }).click();
|
||||
return new ReportsPage(this.page);
|
||||
}
|
||||
|
||||
async goToCashFlowPage() {
|
||||
await this.pageContent.getByRole('link', { name: /^Cash/ }).click();
|
||||
await this.pageContent.getByRole('button', { 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('link')
|
||||
.getByRole('button')
|
||||
.getByRole('heading')
|
||||
.allTextContents();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export class RulesPage {
|
||||
await this._fillEditorFields(
|
||||
data.conditions,
|
||||
this.page.getByTestId('condition-list'),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,28 +64,19 @@ export class RulesPage {
|
||||
}
|
||||
|
||||
if (data.splits) {
|
||||
if (data.splits.beforeSplitActions) {
|
||||
let idx = data.actions?.length ?? 0;
|
||||
for (const splitActions of data.splits) {
|
||||
await this.page.getByTestId('add-split-transactions').click();
|
||||
await this._fillEditorFields(
|
||||
data.splits.beforeSplitActions,
|
||||
this.page.getByTestId('action-list'),
|
||||
splitActions,
|
||||
this.page.getByTestId('action-list').nth(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++;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async _fillEditorFields(data, rootElement) {
|
||||
async _fillEditorFields(data, rootElement, fieldFirst = false) {
|
||||
for (const idx in data) {
|
||||
const { field, op, value } = data[idx];
|
||||
|
||||
@@ -94,15 +86,24 @@ 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.getByRole('button').first().click();
|
||||
await row
|
||||
.getByTestId('field-select')
|
||||
.getByRole('button')
|
||||
.first()
|
||||
.click();
|
||||
await this.page
|
||||
.getByRole('button', { exact: true, name: field })
|
||||
.getByRole('button', { name: field, exact: true })
|
||||
.click();
|
||||
}
|
||||
|
||||
if (op) {
|
||||
await row.getByRole('button', { name: 'is' }).click();
|
||||
if (op && fieldFirst) {
|
||||
await row.getByTestId('op-select').getByRole('button').first().click();
|
||||
await this.page.getByRole('button', { name: op, exact: true }).click();
|
||||
}
|
||||
|
||||
|
||||
@@ -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('Reports', () => {
|
||||
test.describe.parallel('Reports', () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let reportsPage;
|
||||
@@ -43,4 +43,66 @@ test.describe('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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@@ -32,6 +32,7 @@ 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: [
|
||||
{
|
||||
@@ -48,7 +49,6 @@ 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,35 +79,34 @@ test.describe('Rules', () => {
|
||||
value: 'Ikea',
|
||||
},
|
||||
],
|
||||
splits: {
|
||||
beforeSplitActions: [
|
||||
actions: [
|
||||
{
|
||||
op: 'set',
|
||||
field: 'notes',
|
||||
value: 'food / entertainment',
|
||||
},
|
||||
],
|
||||
splits: [
|
||||
[
|
||||
{
|
||||
field: 'notes',
|
||||
value: 'food / entertainment',
|
||||
field: 'a fixed percent of the remainder',
|
||||
value: '90',
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
value: 'Entertainment',
|
||||
},
|
||||
],
|
||||
splitActions: [
|
||||
[
|
||||
{
|
||||
field: 'a fixed percent of the remainder',
|
||||
value: '90',
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
value: 'Entertainment',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
field: 'an equal portion of the remainder',
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
value: 'Food',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
field: 'an equal portion of the remainder',
|
||||
},
|
||||
{
|
||||
field: 'category',
|
||||
value: 'Food',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const accountPage = await navigation.goToAccountPage(
|
||||
@@ -120,7 +119,7 @@ test.describe('Rules', () => {
|
||||
});
|
||||
|
||||
const transaction = accountPage.getNthTransaction(0);
|
||||
await expect(transaction.payee).toHaveText('Split');
|
||||
await expect(transaction.payee).toHaveText('Ikea');
|
||||
await expect(transaction.notes).toHaveText('food / entertainment');
|
||||
await expect(transaction.category).toHaveText('Split');
|
||||
await expect(transaction.debit).toHaveText('100.00');
|
||||
|
||||
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |