@actual-app/core 26.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/.swcrc +11 -0
  2. package/bin/build-browser +40 -0
  3. package/bin/copy-migrations +9 -0
  4. package/db.sqlite +0 -0
  5. package/default-db.sqlite +0 -0
  6. package/migrations/.force-copy-windows +0 -0
  7. package/migrations/1548957970627_remove-db-version.sql +5 -0
  8. package/migrations/1550601598648_payees.sql +23 -0
  9. package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
  10. package/migrations/1561751833510_indexes.sql +7 -0
  11. package/migrations/1567699552727_budget.sql +38 -0
  12. package/migrations/1582384163573_cleared.sql +6 -0
  13. package/migrations/1597756566448_rules.sql +10 -0
  14. package/migrations/1608652596043_parent_field.sql +13 -0
  15. package/migrations/1608652596044_trans_views.sql +56 -0
  16. package/migrations/1612625548236_optimize.sql +7 -0
  17. package/migrations/1614782639336_trans_views2.sql +33 -0
  18. package/migrations/1615745967948_meta.sql +10 -0
  19. package/migrations/1616167010796_accounts_order.sql +5 -0
  20. package/migrations/1618975177358_schedules.sql +28 -0
  21. package/migrations/1632571489012_remove_cache.js +136 -0
  22. package/migrations/1679728867040_rules_conditions.sql +5 -0
  23. package/migrations/1681115033845_add_schedule_name.sql +5 -0
  24. package/migrations/1682974838138_remove_payee_rules.sql +5 -0
  25. package/migrations/1685007876842_add_category_hidden.sql +6 -0
  26. package/migrations/1686139660866_remove_account_type.sql +5 -0
  27. package/migrations/1688749527273_transaction_filters.sql +10 -0
  28. package/migrations/1688841238000_add_account_type.sql +5 -0
  29. package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
  30. package/migrations/1694438752000_add_goal_targets.sql +7 -0
  31. package/migrations/1697046240000_add_reconciled.sql +5 -0
  32. package/migrations/1704572023730_add_account_sync_source.sql +5 -0
  33. package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
  34. package/migrations/1707267033000_reports.sql +28 -0
  35. package/migrations/1712784523000_unhide_input_group.sql +8 -0
  36. package/migrations/1716359441000_include_current.sql +5 -0
  37. package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
  38. package/migrations/1720664867241_add_payee_favorite.sql +5 -0
  39. package/migrations/1720665000000_goal_context.sql +6 -0
  40. package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
  41. package/migrations/1722804019000_create_dashboard_table.js +69 -0
  42. package/migrations/1723665565000_prefs.js +59 -0
  43. package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
  44. package/migrations/1736640000000_custom_report_sorting.sql +7 -0
  45. package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
  46. package/migrations/1738491452000_sorting_rename.sql +13 -0
  47. package/migrations/1739139550000_bank_sync_page.sql +7 -0
  48. package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
  49. package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
  50. package/migrations/1749799110000_add_tags.sql +10 -0
  51. package/migrations/1749799110001_tags_tombstone.sql +5 -0
  52. package/migrations/1754611200000_add_category_template_settings.sql +5 -0
  53. package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
  54. package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
  55. package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
  56. package/migrations/1765518577215_multiple_dashboards.js +30 -0
  57. package/migrations/1768872504000_add_payee_locations.sql +21 -0
  58. package/package.json +128 -0
  59. package/src/mocks/arbitrary-schema.ts +162 -0
  60. package/src/mocks/budget.ts +901 -0
  61. package/src/mocks/files/8859-1.qfx +63 -0
  62. package/src/mocks/files/best.data-ever$.QFX +124 -0
  63. package/src/mocks/files/big.data.QiF +91 -0
  64. package/src/mocks/files/budgets/.commit-to-git +0 -0
  65. package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
  66. package/src/mocks/files/camt/camt.053.xml +463 -0
  67. package/src/mocks/files/credit-card.ofx +11 -0
  68. package/src/mocks/files/data-multi-decimal.ofx +64 -0
  69. package/src/mocks/files/data-payee-memo.ofx +75 -0
  70. package/src/mocks/files/data-payee-memo.qif +17 -0
  71. package/src/mocks/files/data.ofx +124 -0
  72. package/src/mocks/files/data.qfx +124 -0
  73. package/src/mocks/files/data.qif +91 -0
  74. package/src/mocks/files/default-budget-template/db.sqlite +0 -0
  75. package/src/mocks/files/default-budget-template/metadata.json +6 -0
  76. package/src/mocks/files/html-vals.qfx +17 -0
  77. package/src/mocks/index.ts +221 -0
  78. package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
  79. package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
  80. package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
  81. package/src/mocks/random.ts +16 -0
  82. package/src/mocks/setup.ts +180 -0
  83. package/src/mocks/spreadsheet.ts +101 -0
  84. package/src/mocks/util.ts +82 -0
  85. package/src/platform/client/connection/README.md +3 -0
  86. package/src/platform/client/connection/__mocks__/index.ts +67 -0
  87. package/src/platform/client/connection/index-types.ts +95 -0
  88. package/src/platform/client/connection/index.browser.ts +213 -0
  89. package/src/platform/client/connection/index.ts +155 -0
  90. package/src/platform/client/undo/index.ts +59 -0
  91. package/src/platform/exceptions/__mocks__/index.ts +7 -0
  92. package/src/platform/exceptions/index.ts +9 -0
  93. package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
  94. package/src/platform/server/asyncStorage/index-types.ts +35 -0
  95. package/src/platform/server/asyncStorage/index.api.ts +2 -0
  96. package/src/platform/server/asyncStorage/index.electron.ts +88 -0
  97. package/src/platform/server/asyncStorage/index.ts +126 -0
  98. package/src/platform/server/connection/README.md +3 -0
  99. package/src/platform/server/connection/__mocks__/index.ts +15 -0
  100. package/src/platform/server/connection/index-types.ts +20 -0
  101. package/src/platform/server/connection/index.api.ts +13 -0
  102. package/src/platform/server/connection/index.electron.ts +102 -0
  103. package/src/platform/server/connection/index.ts +154 -0
  104. package/src/platform/server/fetch/__mocks__/index.ts +3 -0
  105. package/src/platform/server/fetch/index.api.ts +1 -0
  106. package/src/platform/server/fetch/index.electron.ts +18 -0
  107. package/src/platform/server/fetch/index.ts +20 -0
  108. package/src/platform/server/fs/index.api.ts +198 -0
  109. package/src/platform/server/fs/index.electron.ts +208 -0
  110. package/src/platform/server/fs/index.test.ts +117 -0
  111. package/src/platform/server/fs/index.ts +416 -0
  112. package/src/platform/server/fs/path-join.api.ts +1 -0
  113. package/src/platform/server/fs/path-join.electron.ts +1 -0
  114. package/src/platform/server/fs/path-join.ts +97 -0
  115. package/src/platform/server/fs/shared.ts +33 -0
  116. package/src/platform/server/indexeddb/index.ts +115 -0
  117. package/src/platform/server/log/index.ts +43 -0
  118. package/src/platform/server/sqlite/index.api.ts +2 -0
  119. package/src/platform/server/sqlite/index.electron.ts +134 -0
  120. package/src/platform/server/sqlite/index.test.ts +108 -0
  121. package/src/platform/server/sqlite/index.ts +241 -0
  122. package/src/platform/server/sqlite/normalise.ts +9 -0
  123. package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
  124. package/src/platform/server/sqlite/unicodeLike.ts +31 -0
  125. package/src/server/__mocks__/post.ts +9 -0
  126. package/src/server/__snapshots__/main.test.ts.snap +199 -0
  127. package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
  128. package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
  129. package/src/server/accounts/app-bank-sync.test.ts +136 -0
  130. package/src/server/accounts/app.ts +1294 -0
  131. package/src/server/accounts/link.ts +25 -0
  132. package/src/server/accounts/payees.ts +36 -0
  133. package/src/server/accounts/sync.test.ts +679 -0
  134. package/src/server/accounts/sync.ts +1168 -0
  135. package/src/server/accounts/title/index.ts +60 -0
  136. package/src/server/accounts/title/lower-case.ts +93 -0
  137. package/src/server/accounts/title/specials.ts +21 -0
  138. package/src/server/admin/app.ts +241 -0
  139. package/src/server/api-models.ts +244 -0
  140. package/src/server/api.test.ts +36 -0
  141. package/src/server/api.ts +1030 -0
  142. package/src/server/app.ts +91 -0
  143. package/src/server/aql/compiler.test.ts +966 -0
  144. package/src/server/aql/compiler.ts +1222 -0
  145. package/src/server/aql/exec.test.ts +289 -0
  146. package/src/server/aql/exec.ts +128 -0
  147. package/src/server/aql/index.ts +41 -0
  148. package/src/server/aql/schema/executors.test.ts +420 -0
  149. package/src/server/aql/schema/executors.ts +345 -0
  150. package/src/server/aql/schema/index.test.ts +67 -0
  151. package/src/server/aql/schema/index.ts +409 -0
  152. package/src/server/aql/schema-helpers.test.ts +242 -0
  153. package/src/server/aql/schema-helpers.ts +208 -0
  154. package/src/server/aql/views.test.ts +62 -0
  155. package/src/server/aql/views.ts +57 -0
  156. package/src/server/auth/app.ts +387 -0
  157. package/src/server/bench.ts +29 -0
  158. package/src/server/budget/actions.ts +686 -0
  159. package/src/server/budget/app.ts +469 -0
  160. package/src/server/budget/base.test.ts +340 -0
  161. package/src/server/budget/base.ts +339 -0
  162. package/src/server/budget/category-template-context.test.ts +1658 -0
  163. package/src/server/budget/category-template-context.ts +862 -0
  164. package/src/server/budget/cleanup-template.pegjs +27 -0
  165. package/src/server/budget/cleanup-template.ts +408 -0
  166. package/src/server/budget/envelope.ts +403 -0
  167. package/src/server/budget/goal-template.pegjs +110 -0
  168. package/src/server/budget/goal-template.ts +309 -0
  169. package/src/server/budget/report.ts +308 -0
  170. package/src/server/budget/schedule-template.test.ts +184 -0
  171. package/src/server/budget/schedule-template.ts +351 -0
  172. package/src/server/budget/statements.ts +60 -0
  173. package/src/server/budget/template-notes.test.ts +393 -0
  174. package/src/server/budget/template-notes.ts +323 -0
  175. package/src/server/budget/util.ts +25 -0
  176. package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
  177. package/src/server/budgetfiles/app.ts +672 -0
  178. package/src/server/budgetfiles/backups.test.ts +79 -0
  179. package/src/server/budgetfiles/backups.ts +251 -0
  180. package/src/server/cloud-storage.ts +467 -0
  181. package/src/server/dashboard/app.ts +373 -0
  182. package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
  183. package/src/server/db/index.test.ts +300 -0
  184. package/src/server/db/index.ts +855 -0
  185. package/src/server/db/mappings.ts +59 -0
  186. package/src/server/db/sort.ts +58 -0
  187. package/src/server/db/types/index.ts +342 -0
  188. package/src/server/db/util.ts +36 -0
  189. package/src/server/encryption/app.ts +133 -0
  190. package/src/server/encryption/encryption-internals.api.ts +2 -0
  191. package/src/server/encryption/encryption-internals.electron.ts +89 -0
  192. package/src/server/encryption/encryption-internals.ts +109 -0
  193. package/src/server/encryption/encryption.test.ts +19 -0
  194. package/src/server/encryption/index.test.ts +19 -0
  195. package/src/server/encryption/index.ts +89 -0
  196. package/src/server/errors.ts +110 -0
  197. package/src/server/filters/app.ts +191 -0
  198. package/src/server/importers/actual.ts +49 -0
  199. package/src/server/importers/index.ts +58 -0
  200. package/src/server/importers/ynab4-types.ts +163 -0
  201. package/src/server/importers/ynab4.ts +470 -0
  202. package/src/server/importers/ynab5-types.ts +290 -0
  203. package/src/server/importers/ynab5.ts +1193 -0
  204. package/src/server/main-app.ts +25 -0
  205. package/src/server/main.test.ts +392 -0
  206. package/src/server/main.ts +336 -0
  207. package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
  208. package/src/server/migrate/cli.ts +100 -0
  209. package/src/server/migrate/migrations.test.ts +81 -0
  210. package/src/server/migrate/migrations.ts +192 -0
  211. package/src/server/models.ts +184 -0
  212. package/src/server/mutators.ts +139 -0
  213. package/src/server/notes/app.ts +18 -0
  214. package/src/server/payees/app.ts +351 -0
  215. package/src/server/polyfills.ts +26 -0
  216. package/src/server/post.ts +219 -0
  217. package/src/server/preferences/app.ts +249 -0
  218. package/src/server/prefs.ts +91 -0
  219. package/src/server/reports/app.ts +187 -0
  220. package/src/server/rules/action.ts +344 -0
  221. package/src/server/rules/app.ts +193 -0
  222. package/src/server/rules/condition.ts +436 -0
  223. package/src/server/rules/customFunctions.ts +61 -0
  224. package/src/server/rules/formula-action.test.ts +175 -0
  225. package/src/server/rules/handlebars-helpers.ts +131 -0
  226. package/src/server/rules/index.test.ts +1095 -0
  227. package/src/server/rules/index.ts +22 -0
  228. package/src/server/rules/rule-indexer.ts +89 -0
  229. package/src/server/rules/rule-utils.ts +274 -0
  230. package/src/server/rules/rule.ts +193 -0
  231. package/src/server/schedules/app.test.ts +502 -0
  232. package/src/server/schedules/app.ts +644 -0
  233. package/src/server/schedules/find-schedules.ts +391 -0
  234. package/src/server/server-config.ts +59 -0
  235. package/src/server/sheet.test.ts +101 -0
  236. package/src/server/sheet.ts +280 -0
  237. package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
  238. package/src/server/spreadsheet/app.ts +54 -0
  239. package/src/server/spreadsheet/globals.ts +13 -0
  240. package/src/server/spreadsheet/graph-data-structure.ts +165 -0
  241. package/src/server/spreadsheet/scratch +60 -0
  242. package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
  243. package/src/server/spreadsheet/spreadsheet.ts +523 -0
  244. package/src/server/spreadsheet/util.ts +15 -0
  245. package/src/server/sql/init.sql +88 -0
  246. package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
  247. package/src/server/sync/app.ts +29 -0
  248. package/src/server/sync/encoder.ts +129 -0
  249. package/src/server/sync/index.ts +820 -0
  250. package/src/server/sync/make-test-message.ts +19 -0
  251. package/src/server/sync/migrate.test.ts +169 -0
  252. package/src/server/sync/migrate.ts +48 -0
  253. package/src/server/sync/repair.ts +39 -0
  254. package/src/server/sync/reset.ts +91 -0
  255. package/src/server/sync/sync.property.test.ts +385 -0
  256. package/src/server/sync/sync.test.ts +349 -0
  257. package/src/server/sync/utils.ts +3 -0
  258. package/src/server/tags/app.ts +101 -0
  259. package/src/server/tests/mockData.json +9352 -0
  260. package/src/server/tests/mockSyncServer.ts +119 -0
  261. package/src/server/tools/app.ts +152 -0
  262. package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
  263. package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
  264. package/src/server/transactions/app.ts +136 -0
  265. package/src/server/transactions/export/export-to-csv.ts +132 -0
  266. package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
  267. package/src/server/transactions/import/ofx2json.test.ts +33 -0
  268. package/src/server/transactions/import/ofx2json.ts +157 -0
  269. package/src/server/transactions/import/parse-file.test.ts +224 -0
  270. package/src/server/transactions/import/parse-file.ts +286 -0
  271. package/src/server/transactions/import/qif2json.ts +110 -0
  272. package/src/server/transactions/import/xmlcamt2json.ts +168 -0
  273. package/src/server/transactions/index.ts +196 -0
  274. package/src/server/transactions/merge.test.ts +370 -0
  275. package/src/server/transactions/merge.ts +139 -0
  276. package/src/server/transactions/transaction-rules.test.ts +994 -0
  277. package/src/server/transactions/transaction-rules.ts +1038 -0
  278. package/src/server/transactions/transfer.test.ts +221 -0
  279. package/src/server/transactions/transfer.ts +173 -0
  280. package/src/server/undo.ts +271 -0
  281. package/src/server/update.ts +37 -0
  282. package/src/server/util/budget-name.ts +61 -0
  283. package/src/server/util/custom-sync-mapping.ts +48 -0
  284. package/src/server/util/rschedule.ts +9 -0
  285. package/src/shared/__mocks__/platform.ts +7 -0
  286. package/src/shared/__snapshots__/months.test.ts.snap +21 -0
  287. package/src/shared/arithmetic.test.ts +112 -0
  288. package/src/shared/arithmetic.ts +170 -0
  289. package/src/shared/async.test.ts +135 -0
  290. package/src/shared/async.ts +76 -0
  291. package/src/shared/constants.ts +5 -0
  292. package/src/shared/currencies.ts +70 -0
  293. package/src/shared/dashboard.ts +260 -0
  294. package/src/shared/environment.ts +18 -0
  295. package/src/shared/errors.ts +195 -0
  296. package/src/shared/locale.ts +27 -0
  297. package/src/shared/location-utils.test.ts +69 -0
  298. package/src/shared/location-utils.ts +49 -0
  299. package/src/shared/months.test.ts +5 -0
  300. package/src/shared/months.ts +485 -0
  301. package/src/shared/normalisation.ts +6 -0
  302. package/src/shared/platform.electron.ts +21 -0
  303. package/src/shared/platform.ts +20 -0
  304. package/src/shared/query.ts +176 -0
  305. package/src/shared/rules.test.ts +56 -0
  306. package/src/shared/rules.ts +371 -0
  307. package/src/shared/schedules.test.ts +570 -0
  308. package/src/shared/schedules.ts +560 -0
  309. package/src/shared/test-helpers.ts +156 -0
  310. package/src/shared/transactions.test.ts +275 -0
  311. package/src/shared/transactions.ts +433 -0
  312. package/src/shared/transfer.test.ts +75 -0
  313. package/src/shared/transfer.ts +16 -0
  314. package/src/shared/user.ts +4 -0
  315. package/src/shared/util.test.ts +240 -0
  316. package/src/shared/util.ts +633 -0
  317. package/src/types/api-handlers.ts +287 -0
  318. package/src/types/budget.ts +8 -0
  319. package/src/types/file.ts +47 -0
  320. package/src/types/handlers.ts +46 -0
  321. package/src/types/models/account.ts +24 -0
  322. package/src/types/models/bank-sync.ts +23 -0
  323. package/src/types/models/bank.ts +6 -0
  324. package/src/types/models/category-group.ts +11 -0
  325. package/src/types/models/category.ts +13 -0
  326. package/src/types/models/dashboard.ts +199 -0
  327. package/src/types/models/gocardless.ts +84 -0
  328. package/src/types/models/import-transaction.ts +56 -0
  329. package/src/types/models/index.ts +23 -0
  330. package/src/types/models/nearby-payee.ts +7 -0
  331. package/src/types/models/note.ts +4 -0
  332. package/src/types/models/openid.ts +8 -0
  333. package/src/types/models/payee-location.ts +8 -0
  334. package/src/types/models/payee.ts +10 -0
  335. package/src/types/models/pluggyai.ts +19 -0
  336. package/src/types/models/reports.ts +144 -0
  337. package/src/types/models/rule.ts +174 -0
  338. package/src/types/models/schedule.ts +49 -0
  339. package/src/types/models/simplefin.ts +28 -0
  340. package/src/types/models/tags.ts +6 -0
  341. package/src/types/models/templates.ts +135 -0
  342. package/src/types/models/transaction-filter.ts +9 -0
  343. package/src/types/models/transaction.ts +39 -0
  344. package/src/types/models/user-access.ts +10 -0
  345. package/src/types/models/user.ts +25 -0
  346. package/src/types/prefs.ts +167 -0
  347. package/src/types/server-events.ts +86 -0
  348. package/src/types/server-handlers.ts +27 -0
  349. package/src/types/util.ts +26 -0
  350. package/tsconfig.json +34 -0
  351. package/typings/pegjs.ts +1 -0
  352. package/typings/process-worker.ts +12 -0
  353. package/typings/vite-plugin-peggy-loader.ts +1 -0
  354. package/typings/window.ts +62 -0
  355. package/vite.config.ts +109 -0
  356. package/vite.desktop.config.ts +59 -0
  357. package/vitest.config.ts +43 -0
  358. package/vitest.web.config.ts +38 -0
@@ -0,0 +1,25 @@
1
+ import * as connection from '../platform/server/connection';
2
+ import type { Handlers } from '../types/handlers';
3
+
4
+ import { createApp } from './app';
5
+ import { runHandler } from './mutators';
6
+
7
+ // Main app
8
+ export const app = createApp<Handlers>();
9
+
10
+ app.events.on('sync', event => {
11
+ connection.send('sync-event', event);
12
+ });
13
+
14
+ /**
15
+ * Run a handler by name (server-side). Same API shape as the client connection's send.
16
+ * Used by server code that needs to invoke handlers directly, e.g. importers.
17
+ */
18
+ export async function send<K extends keyof Handlers>(
19
+ name: K,
20
+ args?: Parameters<Handlers[K]>[0],
21
+ ): Promise<Awaited<ReturnType<Handlers[K]>>> {
22
+ return runHandler(app.handlers[name], args, { name }) as Promise<
23
+ Awaited<ReturnType<Handlers[K]>>
24
+ >;
25
+ }
@@ -0,0 +1,392 @@
1
+ // @ts-strict-ignore
2
+ import { deserializeClock, getClock } from '@actual-app/crdt';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+
5
+ import { expectSnapshotWithDiffer } from '../mocks/util';
6
+ import * as connection from '../platform/server/connection';
7
+ import * as fs from '../platform/server/fs';
8
+ import * as monthUtils from '../shared/months';
9
+
10
+ import * as budgetActions from './budget/actions';
11
+ import * as budget from './budget/base';
12
+ import * as db from './db';
13
+ import { handlers } from './main';
14
+ import {
15
+ disableGlobalMutations,
16
+ enableGlobalMutations,
17
+ runHandler,
18
+ runMutator,
19
+ } from './mutators';
20
+ import * as prefs from './prefs';
21
+ import * as sheet from './sheet';
22
+
23
+ vi.mock('./post');
24
+
25
+ beforeEach(async () => {
26
+ await global.emptyDatabase()();
27
+ disableGlobalMutations();
28
+ });
29
+
30
+ afterEach(async () => {
31
+ await runHandler(handlers['close-budget']);
32
+ connection.resetEvents();
33
+ enableGlobalMutations();
34
+ global.currentMonth = null;
35
+ });
36
+
37
+ async function createTestBudget(name) {
38
+ const templatePath = fs.join(__dirname, '/../mocks/files', name);
39
+ const budgetPath = fs.join(__dirname, '/../mocks/files/budgets/test-budget');
40
+ fs._setDocumentDir(fs.join(budgetPath, '..'));
41
+
42
+ await fs.mkdir(budgetPath);
43
+ await fs.copyFile(
44
+ fs.join(templatePath, 'metadata.json'),
45
+ fs.join(budgetPath, 'metadata.json'),
46
+ );
47
+ await fs.copyFile(
48
+ fs.join(templatePath, 'db.sqlite'),
49
+ fs.join(budgetPath, 'db.sqlite'),
50
+ );
51
+ }
52
+
53
+ describe('Budgets', () => {
54
+ afterEach(async () => {
55
+ fs._setDocumentDir(null);
56
+ const budgetPath = fs.join(
57
+ __dirname,
58
+ '/../mocks/files/budgets/test-budget',
59
+ );
60
+
61
+ if (await fs.exists(budgetPath)) {
62
+ await fs.removeDirRecursively(budgetPath);
63
+ }
64
+ });
65
+
66
+ test('budget is successfully loaded', async () => {
67
+ await createTestBudget('default-budget-template');
68
+
69
+ // Grab the clock to compare later
70
+ await db.openDatabase('test-budget');
71
+ const row = await db.first<db.DbClockMessage>(
72
+ 'SELECT * FROM messages_clock',
73
+ );
74
+
75
+ const { error } = await runHandler(handlers['load-budget'], {
76
+ id: 'test-budget',
77
+ });
78
+ expect(error).toBe(undefined);
79
+
80
+ // Make sure the prefs were loaded
81
+ expect(prefs.getPrefs().id).toBe('test-budget');
82
+
83
+ // Make sure the clock has been loaded
84
+ expect(getClock()).toEqual(deserializeClock(row.clock));
85
+ });
86
+
87
+ test('budget detects out of sync migrations', async () => {
88
+ await createTestBudget('default-budget-template');
89
+
90
+ await db.openDatabase('test-budget');
91
+ db.runQuery('INSERT INTO __migrations__ (id) VALUES (1000)');
92
+
93
+ const spy = vi.spyOn(console, 'warn').mockImplementation(() => null);
94
+
95
+ const { error } = await runHandler(handlers['load-budget'], {
96
+ id: 'test-budget',
97
+ });
98
+ // There should be an error and the budget should be unloaded
99
+ expect(error).toBe('out-of-sync-migrations');
100
+ expect(db.getDatabase()).toBe(null);
101
+ expect(prefs.getPrefs()).toBe(null);
102
+
103
+ spy.mockRestore();
104
+ });
105
+ });
106
+
107
+ describe('Accounts', () => {
108
+ test('Transfers are properly updated', async () => {
109
+ await runMutator(async () => {
110
+ await db.insertAccount({ id: 'one', name: 'one' });
111
+ await db.insertAccount({ id: 'two', name: 'two' });
112
+ await db.insertAccount({ id: 'three', name: 'three' });
113
+ await db.insertPayee({
114
+ id: 'transfer-one',
115
+ name: '',
116
+ transfer_acct: 'one',
117
+ });
118
+ await db.insertPayee({
119
+ id: 'transfer-two',
120
+ name: '',
121
+ transfer_acct: 'two',
122
+ });
123
+ await db.insertPayee({
124
+ id: 'transfer-three',
125
+ name: '',
126
+ transfer_acct: 'three',
127
+ });
128
+ });
129
+
130
+ const id = 'test-transfer';
131
+ await runHandler(handlers['transaction-add'], {
132
+ id,
133
+ account: 'one',
134
+ amount: 5000,
135
+ payee: 'transfer-two',
136
+ date: '2017-01-01',
137
+ });
138
+ const differ = expectSnapshotWithDiffer(
139
+ await db.all<db.DbTransaction>('SELECT * FROM transactions'),
140
+ );
141
+
142
+ let transaction = await db.getTransaction(id);
143
+ await runHandler(handlers['transaction-update'], {
144
+ ...(await db.getTransaction(id)),
145
+ payee: 'transfer-three',
146
+ date: '2017-01-03',
147
+ });
148
+ differ.expectToMatchDiff(
149
+ await db.all<db.DbTransaction>('SELECT * FROM transactions'),
150
+ );
151
+
152
+ transaction = await db.getTransaction(id);
153
+ await runHandler(handlers['transaction-delete'], transaction);
154
+ differ.expectToMatchDiff(
155
+ await db.all<db.DbTransaction>('SELECT * FROM transactions'),
156
+ );
157
+ });
158
+ });
159
+
160
+ describe('Budget', () => {
161
+ test('new budgets should be created', async () => {
162
+ const spreadsheet = await sheet.loadSpreadsheet(db);
163
+
164
+ await runMutator(async () => {
165
+ await db.insertCategoryGroup({
166
+ id: 'incomeGroup',
167
+ name: 'incomeGroup',
168
+ is_income: 1,
169
+ });
170
+ await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
171
+ await db.insertCategory({ name: 'foo', cat_group: 'group1' });
172
+ await db.insertCategory({ name: 'bar', cat_group: 'group1' });
173
+ });
174
+
175
+ let bounds = await runHandler(handlers['get-budget-bounds']);
176
+ expect(bounds.start).toBe('2016-10');
177
+ expect(bounds.end).toBe('2018-01');
178
+ expect(spreadsheet.meta().createdMonths).toMatchSnapshot();
179
+
180
+ // Add a transaction (which needs an account) earlier then the
181
+ // current earliest budget to test if it creates the necessary
182
+ // budgets for the earlier months
183
+ db.runQuery("INSERT INTO accounts (id, name) VALUES ('one', 'boa')");
184
+ await runHandler(handlers['transaction-add'], {
185
+ id: uuidv4(),
186
+ date: '2016-05-06',
187
+ amount: 50,
188
+ account: 'one',
189
+ });
190
+
191
+ // Fast-forward in time to a future month and make sure it creates
192
+ // budgets for the months in the future
193
+ global.currentMonth = '2017-02';
194
+
195
+ bounds = await runHandler(handlers['get-budget-bounds']);
196
+ expect(bounds.start).toBe('2016-02');
197
+ expect(bounds.end).toBe('2018-02');
198
+ expect(spreadsheet.meta().createdMonths).toMatchSnapshot();
199
+
200
+ await new Promise(resolve => spreadsheet.onFinish(resolve));
201
+ });
202
+
203
+ test('budget updates when changing a category', async () => {
204
+ const spreadsheet = await sheet.loadSpreadsheet(db);
205
+ function captureChangedCells(func) {
206
+ return new Promise<unknown[]>(resolve => {
207
+ let changed = [];
208
+ const remove = spreadsheet.addEventListener('change', ({ names }) => {
209
+ changed = changed.concat(names);
210
+ });
211
+ func().then(() => {
212
+ remove();
213
+ spreadsheet.onFinish(() => {
214
+ resolve(changed);
215
+ });
216
+ });
217
+ });
218
+ }
219
+
220
+ // Force the system to start tracking these months so budgets are
221
+ // automatically updated when adding/deleting categories
222
+ db.runQuery('INSERT INTO created_budgets (month) VALUES (?)', ['2017-01']);
223
+ db.runQuery('INSERT INTO created_budgets (month) VALUES (?)', ['2017-02']);
224
+ db.runQuery('INSERT INTO created_budgets (month) VALUES (?)', ['2017-03']);
225
+ db.runQuery('INSERT INTO created_budgets (month) VALUES (?)', ['2017-04']);
226
+
227
+ let categories;
228
+ await captureChangedCells(async () => {
229
+ await runMutator(() =>
230
+ db.insertCategoryGroup({ id: 'group1', name: 'group1' }),
231
+ );
232
+ categories = [
233
+ await runHandler(handlers['category-create'], {
234
+ name: 'foo',
235
+ groupId: 'group1',
236
+ }),
237
+ await runHandler(handlers['category-create'], {
238
+ name: 'bar',
239
+ groupId: 'group1',
240
+ }),
241
+ await runHandler(handlers['category-create'], {
242
+ name: 'baz',
243
+ groupId: 'group1',
244
+ }),
245
+ await runHandler(handlers['category-create'], {
246
+ name: 'biz',
247
+ groupId: 'group1',
248
+ }),
249
+ ];
250
+ });
251
+
252
+ db.runQuery("INSERT INTO accounts (id, name) VALUES ('boa', 'boa')");
253
+ const trans = {
254
+ id: 'boa-transaction',
255
+ date: '2017-02-06',
256
+ amount: 5000,
257
+ account: 'boa',
258
+ category: categories[0],
259
+ };
260
+ // Test insertions
261
+ let changed = await captureChangedCells(() =>
262
+ runHandler(handlers['transaction-add'], trans),
263
+ );
264
+ expect(
265
+ changed.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)),
266
+ ).toMatchSnapshot();
267
+ // Test updates
268
+ changed = await captureChangedCells(async () => {
269
+ await runHandler(handlers['transaction-update'], {
270
+ ...(await db.getTransaction(trans.id)),
271
+ amount: 7000,
272
+ });
273
+ });
274
+ expect(
275
+ changed.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)),
276
+ ).toMatchSnapshot();
277
+ // Test deletions
278
+ changed = await captureChangedCells(async () => {
279
+ await runHandler(handlers['transaction-delete'], { id: trans.id });
280
+ });
281
+ expect(
282
+ changed.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)),
283
+ ).toMatchSnapshot();
284
+ });
285
+ });
286
+
287
+ describe('Categories', () => {
288
+ test('can be deleted', async () => {
289
+ await sheet.loadSpreadsheet(db);
290
+
291
+ await runMutator(async () => {
292
+ await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
293
+ await db.insertCategory({ id: 'foo', name: 'foo', cat_group: 'group1' });
294
+ await db.insertCategory({ id: 'bar', name: 'bar', cat_group: 'group1' });
295
+ });
296
+
297
+ let categories = await db.getCategories();
298
+ expect(categories.length).toBe(2);
299
+ expect(categories.find(cat => cat.name === 'foo')).not.toBeNull();
300
+ expect(categories.find(cat => cat.name === 'bar')).not.toBeNull();
301
+ await runHandler(handlers['category-delete'], { id: 'foo' });
302
+
303
+ categories = await db.getCategories();
304
+ expect(categories.length).toBe(1);
305
+ expect(categories.find(cat => cat.name === 'bar')).not.toBeNull();
306
+ });
307
+
308
+ test('transfers properly when deleted', async () => {
309
+ await sheet.loadSpreadsheet(db);
310
+
311
+ const transId = await runMutator(async () => {
312
+ await db.insertCategoryGroup({ id: 'group1', name: 'group1' });
313
+ await db.insertCategoryGroup({ id: 'group1b', name: 'group1b' });
314
+ await db.insertCategoryGroup({
315
+ id: 'group2',
316
+ name: 'group2',
317
+ is_income: 1,
318
+ });
319
+ await db.insertCategory({ id: 'foo', name: 'foo', cat_group: 'group1' });
320
+ await db.insertCategory({ id: 'bar', name: 'bar', cat_group: 'group1b' });
321
+ await db.insertCategory({
322
+ id: 'income1',
323
+ name: 'income1',
324
+ is_income: 1,
325
+ cat_group: 'group2',
326
+ });
327
+ await db.insertCategory({
328
+ id: 'income2',
329
+ name: 'income2',
330
+ is_income: 1,
331
+ cat_group: 'group2',
332
+ });
333
+
334
+ return await db.insertTransaction({
335
+ date: '2017-01-01',
336
+ account: 'acct',
337
+ amount: 4500,
338
+ category: 'foo',
339
+ });
340
+ });
341
+
342
+ await budget.createAllBudgets();
343
+
344
+ // Set a budget value for the category `foo` of 1000
345
+ const sheetName = monthUtils.sheetForMonth('2018-01');
346
+ await budgetActions.setBudget({
347
+ category: 'foo',
348
+ month: '2018-01',
349
+ amount: 1000,
350
+ });
351
+ expect(sheet.getCellValue(sheetName, 'group-budget-group1')).toBe(1000);
352
+ expect(sheet.getCellValue(sheetName, 'group-budget-group1b')).toBe(0);
353
+
354
+ // Make sure the transaction has a category of `foo`
355
+ let trans = await db.getTransaction(transId);
356
+ expect(trans.category).toBe('foo');
357
+
358
+ await runHandler(handlers['category-delete'], {
359
+ id: 'foo',
360
+ transferId: 'bar',
361
+ });
362
+
363
+ // Make sure the transaction has been updated
364
+ trans = await db.getTransaction(transId);
365
+ expect(trans.category).toBe('bar');
366
+
367
+ // Make sure the budget value was transferred
368
+ expect(sheet.getCellValue(sheetName, 'group-budget-group1')).toBe(0);
369
+ expect(sheet.getCellValue(sheetName, 'group-budget-group1b')).toBe(1000);
370
+
371
+ // Transfering an income category to an expense just doesn't make
372
+ // sense. Make sure this doesn't do anything.
373
+ await expect(
374
+ runHandler(handlers['category-delete'], {
375
+ id: 'income1',
376
+ transferId: 'bar',
377
+ }),
378
+ ).rejects.toThrow('Cannot transfer between income and expense categories.');
379
+
380
+ let categories = await db.getCategories();
381
+ expect(categories.find(cat => cat.id === 'income1')).toBeDefined();
382
+
383
+ // Make sure you can delete income categories
384
+ await runHandler(handlers['category-delete'], {
385
+ id: 'income1',
386
+ transferId: 'income2',
387
+ });
388
+
389
+ categories = await db.getCategories();
390
+ expect(categories.find(cat => cat.id === 'income1')).not.toBeDefined();
391
+ });
392
+ });