Compare commits
241 Commits
v24.8.0
...
QueryState
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a27fffc7a | ||
|
|
c25e97b0f6 | ||
|
|
e775306f81 | ||
|
|
02824ad240 | ||
|
|
1a13e98f49 | ||
|
|
3d9e90f797 | ||
|
|
b253246fe2 | ||
|
|
778fc713f3 | ||
|
|
e0f0d8e241 | ||
|
|
310d299ebd | ||
|
|
130f357bab | ||
|
|
f89817170a | ||
|
|
ec37b39e34 | ||
|
|
23f75a6b6a | ||
|
|
f206ba2f0f | ||
|
|
bd5c0cb981 | ||
|
|
3635c8c88a | ||
|
|
5cb97d6f2f | ||
|
|
e8af5b9014 | ||
|
|
328196c485 | ||
|
|
644fe8bdc6 | ||
|
|
15b1b73379 | ||
|
|
8c7e93616f | ||
|
|
a56d6f9e05 | ||
|
|
75acfc79e1 | ||
|
|
300ddc6311 | ||
|
|
05dda5f9d7 | ||
|
|
37ad584826 | ||
|
|
f9c08a995d | ||
|
|
e37a42faf9 | ||
|
|
9f279486ce | ||
|
|
0b3155608c | ||
|
|
3301cfa2fd | ||
|
|
23de23bd4e | ||
|
|
79f640cbc0 | ||
|
|
f786bdcec3 | ||
|
|
f3ae31055e | ||
|
|
21cb684b26 | ||
|
|
e455369443 | ||
|
|
6d122c898d | ||
|
|
e6024f7a8b | ||
|
|
1485d9c871 | ||
|
|
85b3c5714e | ||
|
|
ce4b80f499 | ||
|
|
464d9878c6 | ||
|
|
71c208e444 | ||
|
|
1dce3183e5 | ||
|
|
051c8a6ed0 | ||
|
|
bdeb19424b | ||
|
|
5369494925 | ||
|
|
e653ad33a6 | ||
|
|
a7b8d1251c | ||
|
|
d5e0b7da5d | ||
|
|
279d545a28 | ||
|
|
0b6ea52d9b | ||
|
|
38c5f89c41 | ||
|
|
b774a3b216 | ||
|
|
dc5d1174c7 | ||
|
|
33a7524cd7 | ||
|
|
0a0e26372b | ||
|
|
a28fb93cec | ||
|
|
365da79783 | ||
|
|
df92c80c27 | ||
|
|
d0caf9f521 | ||
|
|
3f85aedd0b | ||
|
|
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 |
@@ -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
|
||||
|
||||
13
.github/workflows/size-compare.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Wait for ${{github.base_ref}} build to succeed
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
uses: fountainhead/action-wait-for-check@v1.2.0
|
||||
id: master-build
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
ref: ${{github.base_ref}}
|
||||
|
||||
- name: Wait for PR build to succeed
|
||||
uses: fountainhead/action-wait-for-check@v1.1.0
|
||||
uses: fountainhead/action-wait-for-check@v1.2.0
|
||||
id: wait-for-build
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
echo "Build failed on PR branch or ${{github.base_ref}}"
|
||||
exit 1
|
||||
- name: Download build artifact from ${{github.base_ref}}
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
id: pr-build
|
||||
with:
|
||||
branch: ${{github.base_ref}}
|
||||
@@ -55,12 +55,13 @@ jobs:
|
||||
path: base
|
||||
|
||||
- name: Download build artifact from PR
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
workflow: build.yml
|
||||
name: build-stats
|
||||
path: head
|
||||
allow_forks: true
|
||||
|
||||
- name: Strip content hashes from stats files
|
||||
run: |
|
||||
@@ -70,14 +71,14 @@ jobs:
|
||||
sed -i -E 's/index\.[0-9a-zA-Z_-]{8,}\./index./g' ./base/web-stats.json
|
||||
sed -i -E 's/\.[0-9a-zA-Z_-]{8,}\.chunk\././g' ./base/web-stats.json
|
||||
sed -i -E 's/\.[0-9a-f]{8,}\././g' ./base/*.json
|
||||
- uses: twk3/rollup-size-compare-action@v1.0.0
|
||||
- uses: twk3/rollup-size-compare-action@v1.1.1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
current-stats-json-path: ./head/web-stats.json
|
||||
base-stats-json-path: ./base/web-stats.json
|
||||
title: desktop-client
|
||||
|
||||
- uses: github/webpack-bundlesize-compare-action@v1.8.2
|
||||
- uses: github/webpack-bundlesize-compare-action@v2.1.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
current-stats-json-path: ./head/loot-core-stats.json
|
||||
|
||||
3
.gitignore
vendored
@@ -50,3 +50,6 @@ bundle.mobile.js.map
|
||||
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
||||
# build output
|
||||
package.tgz
|
||||
|
||||
@@ -64,12 +64,16 @@ Vote for your favorite requests by reacting :+1: to the top comment of the reque
|
||||
|
||||
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"
|
||||
|
||||
25
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",
|
||||
@@ -44,23 +45,27 @@
|
||||
"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.7",
|
||||
"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"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -205,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.9.0",
|
||||
"version": "24.10.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.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
|
||||
|
||||
57
packages/desktop-client/e2e/accounts.mobile.test.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
import { MobileNavigation } from './page-models/mobile-navigation';
|
||||
|
||||
test.describe('Mobile Accounts', () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let configurationPage;
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
navigation = new MobileNavigation(page);
|
||||
configurationPage = new ConfigurationPage(page);
|
||||
|
||||
await page.setViewportSize({
|
||||
width: 350,
|
||||
height: 600,
|
||||
});
|
||||
await page.goto('/');
|
||||
await configurationPage.createTestFile();
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('opens the accounts page and asserts on balances', async () => {
|
||||
const accountsPage = await navigation.goToAccountsPage();
|
||||
|
||||
const account = await accountsPage.getNthAccount(1);
|
||||
|
||||
await expect(account.name).toHaveText('Ally Savings');
|
||||
await expect(account.balance).toHaveText('7,653.00');
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test('opens individual account page and checks that filtering is working', async () => {
|
||||
const accountsPage = await navigation.goToAccountsPage();
|
||||
const accountPage = await accountsPage.openNthAccount(0);
|
||||
|
||||
await expect(accountPage.heading).toHaveText('Bank of America');
|
||||
await expect(accountPage.transactionList).toBeVisible();
|
||||
await expect(await accountPage.getBalance()).toBeGreaterThan(0);
|
||||
await expect(accountPage.noTransactionsMessage).not.toBeVisible();
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
|
||||
await accountPage.searchByText('nothing should be found');
|
||||
await expect(accountPage.noTransactionsMessage).toBeVisible();
|
||||
await expect(accountPage.transactions).toHaveCount(0);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
|
||||
await accountPage.searchByText('Kroger');
|
||||
await expect(accountPage.transactions).not.toHaveCount(0);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -1,3 +1,5 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
@@ -9,7 +11,7 @@ test.describe('Accounts', () => {
|
||||
let configurationPage;
|
||||
let accountPage;
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
navigation = new Navigation(page);
|
||||
configurationPage = new ConfigurationPage(page);
|
||||
@@ -18,7 +20,7 @@ test.describe('Accounts', () => {
|
||||
await configurationPage.createTestFile();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
test.afterEach(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
@@ -99,4 +101,60 @@ test.describe('Accounts', () => {
|
||||
await expect(transaction.account).toHaveText('Ally Savings');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Import Transactions', () => {
|
||||
test.beforeEach(async () => {
|
||||
accountPage = await navigation.createAccount({
|
||||
name: 'CSV import',
|
||||
offBudget: false,
|
||||
balance: 0,
|
||||
});
|
||||
});
|
||||
|
||||
async function importCsv(screenshot = false) {
|
||||
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||
await accountPage.page.getByRole('button', { name: 'Import' }).click();
|
||||
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(join(__dirname, 'data/test.csv'));
|
||||
|
||||
if (screenshot) await expect(page).toMatchThemeScreenshots();
|
||||
|
||||
const importButton = accountPage.page.getByRole('button', {
|
||||
name: /Import \d+ transactions/,
|
||||
});
|
||||
await importButton.click();
|
||||
|
||||
await expect(importButton).not.toBeVisible();
|
||||
}
|
||||
|
||||
test('imports transactions from a CSV file', async () => {
|
||||
await importCsv(true);
|
||||
});
|
||||
|
||||
test('import csv file twice', async () => {
|
||||
await importCsv(false);
|
||||
|
||||
const fileChooserPromise = page.waitForEvent('filechooser');
|
||||
await accountPage.page.getByRole('button', { name: 'Import' }).click();
|
||||
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(join(__dirname, 'data/test.csv'));
|
||||
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
|
||||
const importButton = accountPage.page.getByRole('button', {
|
||||
name: /Import \d+ transactions/,
|
||||
});
|
||||
|
||||
await expect(importButton).toBeDisabled();
|
||||
await expect(await importButton.innerText()).toMatch(
|
||||
/Import 0 transactions/,
|
||||
);
|
||||
|
||||
await accountPage.page.getByRole('button', { name: 'Close' }).click();
|
||||
|
||||
await expect(importButton).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
281
packages/desktop-client/e2e/budget.mobile.test.js
Normal file
@@ -0,0 +1,281 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
|
||||
import { ConfigurationPage } from './page-models/configuration-page';
|
||||
import { MobileNavigation } from './page-models/mobile-navigation';
|
||||
|
||||
const budgetTypes = ['Envelope', 'Tracking'];
|
||||
|
||||
budgetTypes.forEach(budgetType => {
|
||||
test.describe(`Mobile Budget [${budgetType}]`, () => {
|
||||
let page;
|
||||
let navigation;
|
||||
let configurationPage;
|
||||
let previousGlobalIsTesting;
|
||||
|
||||
test.beforeAll(() => {
|
||||
// TODO: Hack, properly mock the currentMonth function
|
||||
previousGlobalIsTesting = global.IS_TESTING;
|
||||
global.IS_TESTING = true;
|
||||
});
|
||||
|
||||
test.afterAll(() => {
|
||||
// TODO: Hack, properly mock the currentMonth function
|
||||
global.IS_TESTING = previousGlobalIsTesting;
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
navigation = new MobileNavigation(page);
|
||||
configurationPage = new ConfigurationPage(page);
|
||||
|
||||
await page.setViewportSize({
|
||||
width: 350,
|
||||
height: 600,
|
||||
});
|
||||
await page.goto('/');
|
||||
await configurationPage.createTestFile();
|
||||
|
||||
if (budgetType === 'Tracking') {
|
||||
// Set budget type to tracking
|
||||
const settingsPage = await navigation.goToSettingsPage();
|
||||
await settingsPage.useBudgetType('tracking');
|
||||
}
|
||||
});
|
||||
|
||||
test.afterEach(async () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('loads the budget page with budgeted amounts', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await expect(budgetPage.categoryNames).toHaveText([
|
||||
'Food',
|
||||
'Restaurants',
|
||||
'Entertainment',
|
||||
'Clothing',
|
||||
'General',
|
||||
'Gift',
|
||||
'Medical',
|
||||
'Savings',
|
||||
'Cell',
|
||||
'Internet',
|
||||
'Mortgage',
|
||||
'Water',
|
||||
'Power',
|
||||
'Starting Balances',
|
||||
'Misc',
|
||||
'Income',
|
||||
]);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
// Page Header Tests
|
||||
|
||||
test('checks that clicking the Actual logo in the page header opens the budget page menu', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await budgetPage.openBudgetPageMenu();
|
||||
|
||||
const budgetPageMenuModal = page.getByRole('dialog');
|
||||
|
||||
await expect(budgetPageMenuModal).toBeVisible();
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test("checks that clicking the left arrow in the page header shows the previous month's budget", async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const selectedMonth = await budgetPage.getSelectedMonth();
|
||||
const displayMonth = monthUtils.format(
|
||||
selectedMonth,
|
||||
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
||||
);
|
||||
|
||||
await expect(budgetPage.heading).toHaveText(displayMonth);
|
||||
|
||||
const previousMonth = await budgetPage.goToPreviousMonth();
|
||||
const previousDisplayMonth = monthUtils.format(
|
||||
previousMonth,
|
||||
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
||||
);
|
||||
|
||||
await expect(budgetPage.heading).toHaveText(previousDisplayMonth);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test('checks that clicking the month in the page header opens the month menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const selectedMonth = await budgetPage.getSelectedMonth();
|
||||
|
||||
await budgetPage.openMonthMenu();
|
||||
|
||||
const monthMenuModal = page.getByRole('dialog');
|
||||
const monthMenuModalHeading = monthMenuModal.getByRole('heading');
|
||||
|
||||
const displayMonth = monthUtils.format(
|
||||
selectedMonth,
|
||||
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
||||
);
|
||||
await expect(monthMenuModalHeading).toHaveText(displayMonth);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test("checks that clicking the right arrow in the page header shows the next month's budget", async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const selectedMonth = await budgetPage.getSelectedMonth();
|
||||
const displayMonth = monthUtils.format(
|
||||
selectedMonth,
|
||||
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
||||
);
|
||||
|
||||
await expect(budgetPage.heading).toHaveText(displayMonth);
|
||||
|
||||
const nextMonth = await budgetPage.goToNextMonth();
|
||||
const nextDisplayMonth = monthUtils.format(
|
||||
nextMonth,
|
||||
budgetPage.MONTH_HEADER_DATE_FORMAT,
|
||||
);
|
||||
|
||||
await expect(budgetPage.heading).toHaveText(nextDisplayMonth);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
// Category / Category Group Menu Tests
|
||||
|
||||
test('checks that clicking the category group name opens the category group menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryGroupName = await budgetPage.getCategoryGroupNameForRow(0);
|
||||
await budgetPage.openCategoryGroupMenu(categoryGroupName);
|
||||
|
||||
const categoryMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(categoryMenuModalHeading).toHaveText(categoryGroupName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test('checks that clicking the category name opens the category menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
await budgetPage.openCategoryMenu(categoryName);
|
||||
|
||||
const categoryMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(categoryMenuModalHeading).toHaveText(categoryName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
// Budgeted Cell Tests
|
||||
|
||||
test('checks that clicking the budgeted cell opens the budget menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
await budgetPage.openBudgetMenu(categoryName);
|
||||
|
||||
const budgetMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(budgetMenuModalHeading).toHaveText(categoryName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
test('updates the budgeted amount', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
|
||||
// Set to 100.00
|
||||
await budgetPage.setBudget(categoryName, 10000);
|
||||
|
||||
const budgetedButton =
|
||||
await budgetPage.getButtonForBudgeted(categoryName);
|
||||
|
||||
await expect(budgetedButton).toHaveText('100.00');
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
// Spent Cell Tests
|
||||
|
||||
test('checks that clicking spent cell redirects to the category transactions page', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
const accountPage = await budgetPage.openSpentPage(categoryName);
|
||||
|
||||
await expect(accountPage.heading).toContainText(categoryName);
|
||||
await expect(accountPage.transactionList).toBeVisible();
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
// Balance Cell Tests
|
||||
|
||||
test('checks that clicking the balance cell opens the balance menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
const categoryName = await budgetPage.getCategoryNameForRow(0);
|
||||
await budgetPage.openBalanceMenu(categoryName);
|
||||
|
||||
const balanceMenuModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(balanceMenuModalHeading).toHaveText(categoryName);
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
|
||||
if (budgetType === 'Envelope') {
|
||||
test('checks that clicking the To Budget/Overbudgeted amount opens the budget summary menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await budgetPage.openEnvelopeBudgetSummaryMenu();
|
||||
|
||||
const summaryModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(summaryModalHeading).toHaveText('Budget Summary');
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
}
|
||||
|
||||
if (budgetType === 'Tracking') {
|
||||
test('checks that clicking the Saved/Projected Savings/Overspent amount opens the budget summary menu modal', async () => {
|
||||
const budgetPage = await navigation.goToBudgetPage();
|
||||
await budgetPage.waitForBudgetTable();
|
||||
|
||||
await budgetPage.openTrackingBudgetSummaryMenu();
|
||||
|
||||
const summaryModalHeading = page
|
||||
.getByRole('dialog')
|
||||
.getByRole('heading');
|
||||
|
||||
await expect(summaryModalHeading).toHaveText('Budget Summary');
|
||||
await expect(page).toMatchThemeScreenshots();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 48 KiB |