mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-30 10:14:53 -05:00
Add unit tests for goal template types (#3183)
* Add unit tests for each goal type * Add test for goals schedule template * Update release notes
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
import * as actions from '../actions';
|
||||||
|
|
||||||
|
import { goalsAverage } from './goalsAverage';
|
||||||
|
|
||||||
|
jest.mock('../actions');
|
||||||
|
|
||||||
|
describe('goalsAverage', () => {
|
||||||
|
const mockGetSheetValue = actions.getSheetValue as jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the amount to budget based on the average of only one previous month of spending', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 1 };
|
||||||
|
const month = '2024-07';
|
||||||
|
const category = { id: 1 };
|
||||||
|
const errors: string[] = [];
|
||||||
|
const to_budget = 0;
|
||||||
|
|
||||||
|
mockGetSheetValue.mockResolvedValueOnce(200);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsAverage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
category,
|
||||||
|
errors,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(-200);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the amount to budget based on the average of multiple previous months of spending', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 4 };
|
||||||
|
const month = '2024-08';
|
||||||
|
const category = { id: 1 };
|
||||||
|
const errors: string[] = [];
|
||||||
|
const to_budget = 0;
|
||||||
|
|
||||||
|
mockGetSheetValue
|
||||||
|
.mockResolvedValueOnce(200)
|
||||||
|
.mockResolvedValueOnce(300)
|
||||||
|
.mockResolvedValueOnce(100)
|
||||||
|
.mockResolvedValueOnce(400);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsAverage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
category,
|
||||||
|
errors,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(-250);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error when template amount passed in is <= 0', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 0 };
|
||||||
|
const month = '2024-08';
|
||||||
|
const category = { id: 1 };
|
||||||
|
const errors: string[] = [];
|
||||||
|
const to_budget = 1000;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsAverage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
category,
|
||||||
|
errors,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(1000);
|
||||||
|
expect(result.errors).toStrictEqual([
|
||||||
|
'Number of months to average is not valid',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
134
packages/loot-core/src/server/budget/goals/goalsBy.test.ts
Normal file
134
packages/loot-core/src/server/budget/goals/goalsBy.test.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import * as actions from '../actions';
|
||||||
|
|
||||||
|
import { goalsBy } from './goalsBy';
|
||||||
|
|
||||||
|
jest.mock('../actions');
|
||||||
|
|
||||||
|
describe('goalsBy', () => {
|
||||||
|
const mockIsReflectBudget = actions.isReflectBudget as jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount with target in the future and no current balance', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 100, month: '2024-12' };
|
||||||
|
const current_month = '2024-08';
|
||||||
|
const last_month_balance = 0;
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const template_lines = [template];
|
||||||
|
const l = 0;
|
||||||
|
const remainder = 0;
|
||||||
|
mockIsReflectBudget.mockReturnValue(false);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsBy(
|
||||||
|
template_lines,
|
||||||
|
current_month,
|
||||||
|
template,
|
||||||
|
l,
|
||||||
|
remainder,
|
||||||
|
last_month_balance,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(2000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.remainder).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount with target in the future and existing balance towards goal', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 100, month: '2024-12' };
|
||||||
|
const current_month = '2024-08';
|
||||||
|
const last_month_balance = 5000;
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const template_lines = [template];
|
||||||
|
const l = 0;
|
||||||
|
const remainder = 0;
|
||||||
|
mockIsReflectBudget.mockReturnValue(false);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsBy(
|
||||||
|
template_lines,
|
||||||
|
current_month,
|
||||||
|
template,
|
||||||
|
l,
|
||||||
|
remainder,
|
||||||
|
last_month_balance,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(1000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.remainder).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount when target balance met early', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 100, month: '2024-12' };
|
||||||
|
const current_month = '2024-08';
|
||||||
|
const last_month_balance = 10000;
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const template_lines = [template];
|
||||||
|
const l = 0;
|
||||||
|
const remainder = 0;
|
||||||
|
mockIsReflectBudget.mockReturnValue(false);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsBy(
|
||||||
|
template_lines,
|
||||||
|
current_month,
|
||||||
|
template,
|
||||||
|
l,
|
||||||
|
remainder,
|
||||||
|
last_month_balance,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(0);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.remainder).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error when budget is a reflect budget', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: -100, month: '2024-08', repeat: 1 };
|
||||||
|
const current_month = '2024-08';
|
||||||
|
const last_month_balance = 0;
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const template_lines = [template];
|
||||||
|
const l = 0;
|
||||||
|
const remainder = 0;
|
||||||
|
mockIsReflectBudget.mockReturnValue(true);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsBy(
|
||||||
|
template_lines,
|
||||||
|
current_month,
|
||||||
|
template,
|
||||||
|
l,
|
||||||
|
remainder,
|
||||||
|
last_month_balance,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(0);
|
||||||
|
expect(result.errors).toStrictEqual([
|
||||||
|
'by templates are not supported in Report budgets',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import * as db from '../../db';
|
||||||
|
import * as actions from '../actions';
|
||||||
|
|
||||||
|
import { goalsPercentage } from './goalsPercentage';
|
||||||
|
|
||||||
|
jest.mock('../actions');
|
||||||
|
jest.mock('../../db');
|
||||||
|
|
||||||
|
describe('goalsPercentage', () => {
|
||||||
|
const mockGetSheetValue = actions.getSheetValue as jest.Mock;
|
||||||
|
const mockGetCategories = db.getCategories as jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget based on a percentage of all income for the current month', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { percent: 10, category: 'all income' };
|
||||||
|
const month = '2024-08';
|
||||||
|
const available_start = 0;
|
||||||
|
const sheetName = '2024-08';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
mockGetSheetValue.mockResolvedValueOnce(1000);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsPercentage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
available_start,
|
||||||
|
sheetName,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(100);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget based on a percentage of all income for the previous month', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { percent: 10, category: 'all income', previous: true };
|
||||||
|
const month = '2024-08';
|
||||||
|
const available_start = 0;
|
||||||
|
const sheetName = '2024-08';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
mockGetSheetValue.mockResolvedValueOnce(1000);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsPercentage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
available_start,
|
||||||
|
sheetName,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(100);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget based on a percentage of available funds', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { percent: 10, category: 'available funds' };
|
||||||
|
const month = '2024-08';
|
||||||
|
const available_start = 1000;
|
||||||
|
const sheetName = '2024-08';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsPercentage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
available_start,
|
||||||
|
sheetName,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(100);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget based on a percentage of a specific income category for the current month', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { percent: 10, category: 'Salary' };
|
||||||
|
const month = '2024-08';
|
||||||
|
const available_start = 0;
|
||||||
|
const sheetName = '2024-08';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
mockGetCategories.mockResolvedValueOnce([
|
||||||
|
{ id: 1, name: 'Salary', is_income: true },
|
||||||
|
]);
|
||||||
|
mockGetSheetValue.mockResolvedValueOnce(1000);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsPercentage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
available_start,
|
||||||
|
sheetName,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(100);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget based on a percentage of a specific income category for the previous month', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { percent: 10, category: 'Salary', previous: true };
|
||||||
|
const month = '2024-08';
|
||||||
|
const available_start = 0;
|
||||||
|
const sheetName = '2024-08';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
mockGetCategories.mockResolvedValueOnce([
|
||||||
|
{ id: 1, name: 'Salary', is_income: true },
|
||||||
|
]);
|
||||||
|
mockGetSheetValue.mockResolvedValueOnce(1000);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsPercentage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
available_start,
|
||||||
|
sheetName,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(100);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an error if the specified income category does not exist', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { percent: 10, category: 'NonExistentCategory' };
|
||||||
|
const month = '2024-08';
|
||||||
|
const available_start = 0;
|
||||||
|
const sheetName = '2024-08';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
mockGetCategories.mockResolvedValueOnce([
|
||||||
|
{ id: 1, name: 'Salary', is_income: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsPercentage(
|
||||||
|
template,
|
||||||
|
month,
|
||||||
|
available_start,
|
||||||
|
sheetName,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(0);
|
||||||
|
expect(result.errors).toStrictEqual([
|
||||||
|
'Could not find category “NonExistentCategory”',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import { goalsRemainder } from './goalsRemainder';
|
||||||
|
|
||||||
|
describe('goalsRemainder', () => {
|
||||||
|
it('should calculate the budget correctly when remainder_scale is greater than 0', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { weight: 100 };
|
||||||
|
const budgetAvailable = 1000;
|
||||||
|
const remainder_scale = 0.5;
|
||||||
|
const to_budget = 0;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsRemainder(
|
||||||
|
template,
|
||||||
|
budgetAvailable,
|
||||||
|
remainder_scale,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget correctly when remainder_scale is 0', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { weight: 100 };
|
||||||
|
const budgetAvailable = 1000;
|
||||||
|
const remainder_scale = 0;
|
||||||
|
const to_budget = 0;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsRemainder(
|
||||||
|
template,
|
||||||
|
budgetAvailable,
|
||||||
|
remainder_scale,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget correctly when when the calculated budget exceeds the budget available', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { weight: 1000 };
|
||||||
|
const budgetAvailable = 500;
|
||||||
|
const remainder_scale = 1;
|
||||||
|
const to_budget = 0;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsRemainder(
|
||||||
|
template,
|
||||||
|
budgetAvailable,
|
||||||
|
remainder_scale,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should calculate the budget correctly when there is 1 minor unit leftover from rounding', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { weight: 499 };
|
||||||
|
const budgetAvailable = 500;
|
||||||
|
const remainder_scale = 1;
|
||||||
|
const to_budget = 0;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsRemainder(
|
||||||
|
template,
|
||||||
|
budgetAvailable,
|
||||||
|
remainder_scale,
|
||||||
|
to_budget,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(500);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import * as db from '../../db';
|
||||||
|
import { getRuleForSchedule } from '../../schedules/app';
|
||||||
|
import { isReflectBudget } from '../actions';
|
||||||
|
|
||||||
|
import { goalsSchedule } from './goalsSchedule';
|
||||||
|
|
||||||
|
jest.mock('../../db');
|
||||||
|
jest.mock('../actions');
|
||||||
|
jest.mock('../../schedules/app', () => {
|
||||||
|
const actualModule = jest.requireActual('../../schedules/app');
|
||||||
|
return {
|
||||||
|
...actualModule,
|
||||||
|
getRuleForSchedule: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('goalsSchedule', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget when recurring schedule set', async () => {
|
||||||
|
// Given
|
||||||
|
const scheduleFlag = false;
|
||||||
|
const template_lines = [{ type: 'schedule', name: 'Test Schedule' }];
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const balance = 0;
|
||||||
|
const remainder = 0;
|
||||||
|
const last_month_balance = 0;
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const category = { id: 1, name: 'Test Category' };
|
||||||
|
|
||||||
|
(db.first as jest.Mock).mockResolvedValue({ id: 1, completed: 0 });
|
||||||
|
(getRuleForSchedule as jest.Mock).mockResolvedValue({
|
||||||
|
serialize: () => ({
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
op: 'is',
|
||||||
|
field: 'date',
|
||||||
|
value: {
|
||||||
|
start: '2024-08-01',
|
||||||
|
interval: 1,
|
||||||
|
frequency: 'monthly',
|
||||||
|
patterns: [],
|
||||||
|
skipWeekend: false,
|
||||||
|
weekendSolveMode: 'before',
|
||||||
|
endMode: 'never',
|
||||||
|
endOccurrences: 1,
|
||||||
|
endDate: '2024-08-04',
|
||||||
|
},
|
||||||
|
type: 'date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
op: 'is',
|
||||||
|
field: 'amount',
|
||||||
|
value: -10000,
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
execActions: () => ({ amount: -10000, subtransactions: [] }),
|
||||||
|
});
|
||||||
|
(isReflectBudget as jest.Mock).mockReturnValue(false);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSchedule(
|
||||||
|
scheduleFlag,
|
||||||
|
template_lines,
|
||||||
|
current_month,
|
||||||
|
balance,
|
||||||
|
remainder,
|
||||||
|
last_month_balance,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
category,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(10000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.remainder).toBe(0);
|
||||||
|
expect(result.scheduleFlag).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
147
packages/loot-core/src/server/budget/goals/goalsSimple.test.ts
Normal file
147
packages/loot-core/src/server/budget/goals/goalsSimple.test.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { goalsSimple } from './goalsSimple';
|
||||||
|
|
||||||
|
describe('goalsSimple', () => {
|
||||||
|
it('should return correct budget amount when limit set and no balance left from previous months', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { limit: { amount: 100, hold: false } };
|
||||||
|
const limitCheck = false;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const limit = 0;
|
||||||
|
const hold = false;
|
||||||
|
const to_budget = 0;
|
||||||
|
const last_month_balance = 0;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSimple(
|
||||||
|
template,
|
||||||
|
limitCheck,
|
||||||
|
errors,
|
||||||
|
limit,
|
||||||
|
hold,
|
||||||
|
to_budget,
|
||||||
|
last_month_balance,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(10000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limitCheck).toBe(true);
|
||||||
|
expect(result.limit).toBe(10000);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount when limit set and balance from previous month left over', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { limit: { amount: 100, hold: false } };
|
||||||
|
const limitCheck = false;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const limit = 0;
|
||||||
|
const hold = false;
|
||||||
|
const to_budget = 0;
|
||||||
|
const last_month_balance = 2000;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSimple(
|
||||||
|
template,
|
||||||
|
limitCheck,
|
||||||
|
errors,
|
||||||
|
limit,
|
||||||
|
hold,
|
||||||
|
to_budget,
|
||||||
|
last_month_balance,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(8000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limitCheck).toBe(true);
|
||||||
|
expect(result.limit).toBe(10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount when assigned from previous month is greater than the limit set', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { limit: { amount: 100, hold: false } };
|
||||||
|
const limitCheck = false;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const limit = 0;
|
||||||
|
const hold = false;
|
||||||
|
const to_budget = 0;
|
||||||
|
const last_month_balance = 20000;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSimple(
|
||||||
|
template,
|
||||||
|
limitCheck,
|
||||||
|
errors,
|
||||||
|
limit,
|
||||||
|
hold,
|
||||||
|
to_budget,
|
||||||
|
last_month_balance,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(-10000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limitCheck).toBe(true);
|
||||||
|
expect(result.limit).toBe(10000);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount when both limit and monthly limit set', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { monthly: 50, limit: { amount: 100, hold: false } };
|
||||||
|
const limitCheck = false;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const limit = 0;
|
||||||
|
const hold = false;
|
||||||
|
const to_budget = 0;
|
||||||
|
const last_month_balance = 0;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSimple(
|
||||||
|
template,
|
||||||
|
limitCheck,
|
||||||
|
errors,
|
||||||
|
limit,
|
||||||
|
hold,
|
||||||
|
to_budget,
|
||||||
|
last_month_balance,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(5000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limitCheck).toBe(true);
|
||||||
|
expect(result.limit).toBe(10000);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when multiple limit checks exist', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { limit: { amount: 100, hold: true } };
|
||||||
|
const limitCheck = true;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const limit = 0;
|
||||||
|
const hold = true;
|
||||||
|
const to_budget = 0;
|
||||||
|
const last_month_balance = 200;
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSimple(
|
||||||
|
template,
|
||||||
|
limitCheck,
|
||||||
|
errors,
|
||||||
|
limit,
|
||||||
|
hold,
|
||||||
|
to_budget,
|
||||||
|
last_month_balance,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(0);
|
||||||
|
expect(result.errors).toStrictEqual(['More than one “up to” limit found.']);
|
||||||
|
expect(result.limitCheck).toBe(true);
|
||||||
|
expect(result.limit).toBe(0);
|
||||||
|
expect(result.hold).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
105
packages/loot-core/src/server/budget/goals/goalsSpend.test.ts
Normal file
105
packages/loot-core/src/server/budget/goals/goalsSpend.test.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { getSheetValue } from '../actions';
|
||||||
|
|
||||||
|
import { goalsSpend } from './goalsSpend';
|
||||||
|
|
||||||
|
jest.mock('../actions');
|
||||||
|
|
||||||
|
describe('goalsSpend', () => {
|
||||||
|
const mockGetSheetValue = getSheetValue as jest.Mock;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount for range when no spending has happened', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 60, from: '2024-01', month: '2024-12' };
|
||||||
|
const last_month_balance = 0;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const category = { id: 'uuid' };
|
||||||
|
|
||||||
|
mockGetSheetValue
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSpend(
|
||||||
|
template,
|
||||||
|
last_month_balance,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
category,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(500);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct budget amount for range when spending has happened', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 60, from: '2024-01', month: '2024-12' };
|
||||||
|
const last_month_balance = 0;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const category = { id: 'uuid' };
|
||||||
|
|
||||||
|
mockGetSheetValue
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500)
|
||||||
|
.mockResolvedValueOnce(500);
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSpend(
|
||||||
|
template,
|
||||||
|
last_month_balance,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
category,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(600);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error when range is in the past', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 60, from: '2024-01', month: '2024-05' };
|
||||||
|
const last_month_balance = 0;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
const category = { id: 'uuid' };
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsSpend(
|
||||||
|
template,
|
||||||
|
last_month_balance,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
category,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(0);
|
||||||
|
expect(result.errors).toStrictEqual(['2024-05 is in the past.']);
|
||||||
|
});
|
||||||
|
});
|
||||||
124
packages/loot-core/src/server/budget/goals/goalsWeek.test.ts
Normal file
124
packages/loot-core/src/server/budget/goals/goalsWeek.test.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { goalsWeek } from './goalsWeek';
|
||||||
|
|
||||||
|
describe('goalsWeek', () => {
|
||||||
|
it('should return the correct budget amount for a weekly repeat', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 100, starting: '2024-08-01', weeks: 1 };
|
||||||
|
const limit = 0;
|
||||||
|
const limitCheck = false;
|
||||||
|
const hold = false;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsWeek(
|
||||||
|
template,
|
||||||
|
limit,
|
||||||
|
limitCheck,
|
||||||
|
hold,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(50000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limit).toBe(0);
|
||||||
|
expect(result.limitCheck).toBe(false);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct budget amount for a bi-weekly repeat', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: '100', starting: '2024-08-01', weeks: 2 };
|
||||||
|
const limit = 0;
|
||||||
|
const limitCheck = false;
|
||||||
|
const hold = false;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsWeek(
|
||||||
|
template,
|
||||||
|
limit,
|
||||||
|
limitCheck,
|
||||||
|
hold,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(30000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limit).toBe(0);
|
||||||
|
expect(result.limitCheck).toBe(false);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the correct budget when limit set', async () => {
|
||||||
|
// Given
|
||||||
|
const template = { amount: 100, starting: '2024-08-01', weeks: 1 };
|
||||||
|
const limit = 20000;
|
||||||
|
const limitCheck = false;
|
||||||
|
const hold = false;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsWeek(
|
||||||
|
template,
|
||||||
|
limit,
|
||||||
|
limitCheck,
|
||||||
|
hold,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(50000);
|
||||||
|
expect(result.errors).toHaveLength(0);
|
||||||
|
expect(result.limit).toBe(20000);
|
||||||
|
expect(result.limitCheck).toBe(false);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return error when multiple limit checks exist', async () => {
|
||||||
|
// Given
|
||||||
|
const template = {
|
||||||
|
amount: '100',
|
||||||
|
starting: '2024-08-01',
|
||||||
|
weeks: 1,
|
||||||
|
limit: { amount: 100, hold: true },
|
||||||
|
};
|
||||||
|
const limit = 1000;
|
||||||
|
const limitCheck = true;
|
||||||
|
const hold = false;
|
||||||
|
const current_month = '2024-08-01';
|
||||||
|
const to_budget = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// When
|
||||||
|
const result = await goalsWeek(
|
||||||
|
template,
|
||||||
|
limit,
|
||||||
|
limitCheck,
|
||||||
|
hold,
|
||||||
|
current_month,
|
||||||
|
to_budget,
|
||||||
|
errors,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
expect(result.to_budget).toBe(0);
|
||||||
|
expect(result.errors).toStrictEqual(['More than one “up to” limit found.']);
|
||||||
|
expect(result.limit).toBe(1000);
|
||||||
|
expect(result.limitCheck).toBe(true);
|
||||||
|
expect(result.hold).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
6
upcoming-release-notes/3183.md
Normal file
6
upcoming-release-notes/3183.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
category: Maintenance
|
||||||
|
authors: [ ACWalker ]
|
||||||
|
---
|
||||||
|
|
||||||
|
Add unit tests for the existing goal template types.
|
||||||
Reference in New Issue
Block a user