mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 12:43:09 -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