diff --git a/packages/loot-core/src/server/budget/goals/goalsAverage.test.ts b/packages/loot-core/src/server/budget/goals/goalsAverage.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..07285d34c160d641eeeb4dd3cf5ed86bd448fd66
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsAverage.test.ts
@@ -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',
+    ]);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsBy.test.ts b/packages/loot-core/src/server/budget/goals/goalsBy.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cafec5ae7167b144635e4ff74a90de8e80645071
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsBy.test.ts
@@ -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',
+    ]);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsPercentage.test.ts b/packages/loot-core/src/server/budget/goals/goalsPercentage.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4dca6ac3706b49ba3adb3d8fe54952db66815da5
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsPercentage.test.ts
@@ -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”',
+    ]);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsRemainder.test.ts b/packages/loot-core/src/server/budget/goals/goalsRemainder.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b68b9897d96e4adffc5cff33162548621d231672
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsRemainder.test.ts
@@ -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);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts b/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..abfa9c035d804996b869ccd893ec1f9e1b2039d9
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsSchedule.test.ts
@@ -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);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsSimple.test.ts b/packages/loot-core/src/server/budget/goals/goalsSimple.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fd7f2ac2ed15418267b358330617d0347b82ed9b
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsSimple.test.ts
@@ -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);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsSpend.test.ts b/packages/loot-core/src/server/budget/goals/goalsSpend.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0b69e800e94dcb2626a205f290beb57b4d9dca27
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsSpend.test.ts
@@ -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.']);
+  });
+});
diff --git a/packages/loot-core/src/server/budget/goals/goalsWeek.test.ts b/packages/loot-core/src/server/budget/goals/goalsWeek.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c1351ca8cb7c50b20b0a9fdded62c13f1502c5a6
--- /dev/null
+++ b/packages/loot-core/src/server/budget/goals/goalsWeek.test.ts
@@ -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);
+  });
+});
diff --git a/upcoming-release-notes/3183.md b/upcoming-release-notes/3183.md
new file mode 100644
index 0000000000000000000000000000000000000000..852936ebd44a4eb2624d8e8cbd3aba75f263dd4d
--- /dev/null
+++ b/upcoming-release-notes/3183.md
@@ -0,0 +1,6 @@
+---
+category: Maintenance
+authors: [ ACWalker ]
+---
+
+Add unit tests for the existing goal template types.