diff --git a/src/common/components/DownloadAllocationWorksheetModal/DownloadAllocationWorksheetModal.test.js b/src/common/components/DownloadAllocationWorksheetModal/DownloadAllocationWorksheetModal.test.js
new file mode 100644
index 00000000..0c6a6d28
--- /dev/null
+++ b/src/common/components/DownloadAllocationWorksheetModal/DownloadAllocationWorksheetModal.test.js
@@ -0,0 +1,103 @@
+import {
+ QueryClient,
+ QueryClientProvider,
+} from 'react-query';
+
+import {
+ render,
+ screen,
+ waitFor,
+} from '@folio/jest-config-stripes/testing-library/react';
+import userEvent from '@folio/jest-config-stripes/testing-library/user-event';
+import { exportToCsv } from '@folio/stripes/components';
+import { useOkapiKy } from '@folio/stripes/core';
+import { useShowCallout } from '@folio/stripes-acq-components';
+
+import { fetchFinanceData } from '../../utils';
+import { DownloadAllocationWorksheetModal } from './DownloadAllocationWorksheetModal';
+import { useWorksheetFiscalYears } from './useWorksheetFiscalYears';
+
+jest.mock('@folio/stripes/components', () => ({
+ ...jest.requireActual('@folio/stripes/components'),
+ exportToCsv: jest.fn(),
+}));
+jest.mock('@folio/stripes-acq-components', () => ({
+ ...jest.requireActual('@folio/stripes-acq-components'),
+ useShowCallout: jest.fn(),
+}));
+jest.mock('../../utils', () => ({
+ fetchFinanceData: jest.fn(),
+}));
+jest.mock('./useWorksheetFiscalYears', () => ({
+ useWorksheetFiscalYears: jest.fn(),
+}));
+
+const queryClient = new QueryClient();
+const renderComponent = (props) => render(
+
+
+ ,
+);
+
+const fiscalYears = [{ id: 'fy1', code: 'FY2021' }];
+
+describe('DownloadAllocationWorksheetModal', () => {
+ const toggleMock = jest.fn();
+ const showCalloutMock = jest.fn();
+ const kyMock = { get: jest.fn() };
+
+ beforeEach(() => {
+ fetchFinanceData.mockReturnValue(() => ({ fyFinanceData: [{ fiscalYearId: 'fy1' }] }));
+ useOkapiKy.mockReturnValue(kyMock);
+ useShowCallout.mockReturnValue(showCalloutMock);
+ useWorksheetFiscalYears
+ .mockImplementationOnce((_, options) => {
+ options?.onSuccess?.({ fiscalYears });
+
+ return { isFetching: false, fiscalYears };
+ })
+ .mockReturnValue({ isFetching: false, fiscalYears });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should render modal with fiscal year selection', () => {
+ renderComponent({ open: true, toggle: toggleMock });
+
+ expect(screen.getByText('ui-finance.selectFiscalYear')).toBeInTheDocument();
+ expect(screen.getByText('ui-finance.fiscalyear')).toBeInTheDocument();
+ expect(screen.getByText('stripes-core.button.confirm')).toBeInTheDocument();
+ });
+
+ it('should call toggle on cancel', async () => {
+ renderComponent({ open: true, toggle: toggleMock });
+
+ await userEvent.click(screen.getByRole('button', { name: 'stripes-components.cancel' }));
+
+ expect(toggleMock).toHaveBeenCalled();
+ });
+
+ it('should enable confirm button when fiscal year is selected', () => {
+ renderComponent({ open: true, toggle: toggleMock });
+
+ expect(screen.getByText('stripes-core.button.confirm')).not.toBeDisabled();
+ });
+
+ it('should call showCallout and toggle on successful confirm', async () => {
+ kyMock.get.mockResolvedValue({
+ json: jest.fn().mockResolvedValue({ fyFinanceData: [] }),
+ });
+
+ renderComponent({ open: true, toggle: toggleMock });
+
+ await userEvent.click(screen.getByRole('button', { name: 'stripes-core.button.confirm' }));
+
+ await waitFor(() => {
+ expect(exportToCsv).toHaveBeenCalled();
+ expect(showCalloutMock).toHaveBeenCalledWith({ messageId: 'ui-finance.allocation.worksheet.download.start' });
+ expect(toggleMock).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/common/hooks/useGroupUpcomingFiscalYears/useGroupUpcomingFiscalYears.test.js b/src/common/hooks/useGroupUpcomingFiscalYears/useGroupUpcomingFiscalYears.test.js
new file mode 100644
index 00000000..91d4564d
--- /dev/null
+++ b/src/common/hooks/useGroupUpcomingFiscalYears/useGroupUpcomingFiscalYears.test.js
@@ -0,0 +1,67 @@
+import {
+ QueryClient,
+ QueryClientProvider,
+} from 'react-query';
+
+import {
+ renderHook,
+ waitFor,
+} from '@folio/jest-config-stripes/testing-library/react';
+import { useOkapiKy } from '@folio/stripes/core';
+
+import {
+ getGroupLedgers,
+ getLedgersCurrentFiscalYears,
+} from '../../../Groups/GroupDetails/utils';
+import { FISCAL_YEARS_API } from '../../const';
+import { useGroupUpcomingFiscalYears } from './useGroupUpcomingFiscalYears';
+
+jest.mock('../../../Groups/GroupDetails/utils', () => ({
+ getGroupLedgers: jest.fn(),
+ getLedgersCurrentFiscalYears: jest.fn(),
+}));
+
+const queryClient = new QueryClient();
+const wrapper = ({ children }) => (
+
+ {children}
+
+);
+
+describe('useGroupUpcomingFiscalYears', () => {
+ const kyMock = {
+ get: jest.fn(() => ({
+ json: jest.fn().mockResolvedValue({ fiscalYears: [], totalRecords: 0 }),
+ })),
+ extend: jest.fn(() => kyMock),
+ };
+
+ beforeEach(() => {
+ useOkapiKy.mockReturnValue(kyMock);
+ getGroupLedgers.mockReturnValue(() => Promise.resolve({ ledgers: [{ id: 'ledger1' }] }));
+ getLedgersCurrentFiscalYears.mockReturnValue(() => Promise.resolve([{ series: 'FY', periodStart: '2022-01-01' }]));
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should return default data when groupId is not provided', async () => {
+ const { result } = renderHook(() => useGroupUpcomingFiscalYears(), { wrapper });
+
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
+
+ expect(result.current.fiscalYears).toEqual([]);
+ expect(result.current.totalRecords).toBeUndefined();
+ });
+
+ it('should fetch and return fiscal years data', async () => {
+ const { result } = renderHook(() => useGroupUpcomingFiscalYears('groupId'), { wrapper });
+
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
+
+ expect(result.current.fiscalYears).toEqual([]);
+ expect(result.current.totalRecords).toBe(0);
+ expect(kyMock.get).toHaveBeenCalledWith(FISCAL_YEARS_API, expect.any(Object));
+ });
+});
diff --git a/src/common/hooks/useLedgerUpcomingFiscalYears/useLedgerUpcomingFiscalYears.test.js b/src/common/hooks/useLedgerUpcomingFiscalYears/useLedgerUpcomingFiscalYears.test.js
new file mode 100644
index 00000000..0ecdc9bf
--- /dev/null
+++ b/src/common/hooks/useLedgerUpcomingFiscalYears/useLedgerUpcomingFiscalYears.test.js
@@ -0,0 +1,63 @@
+import {
+ QueryClient,
+ QueryClientProvider,
+} from 'react-query';
+
+import {
+ renderHook,
+ waitFor,
+} from '@folio/jest-config-stripes/testing-library/react';
+import { useOkapiKy } from '@folio/stripes/core';
+
+import { FISCAL_YEARS_API } from '../../const';
+import { useLedgerCurrentFiscalYear } from '../useLedgerCurrentFiscalYear';
+import { useLedgerUpcomingFiscalYears } from './useLedgerUpcomingFiscalYears';
+
+jest.mock('../useLedgerCurrentFiscalYear', () => ({
+ useLedgerCurrentFiscalYear: jest.fn(),
+}));
+
+const currentFiscalYear = {
+ id: 'fiscal-year-id',
+ series: 'FY',
+ periodStart: '2023-10-05T00:00:00Z',
+};
+const fiscalYears = [{ id: 'fy1' }, { id: 'fy2' }];
+
+const kyMock = {
+ get: jest.fn(() => ({
+ json: () => Promise.resolve({ fiscalYears }),
+ })),
+};
+
+const queryClient = new QueryClient();
+const wrapper = ({ children }) => (
+
+ {children}
+
+);
+
+describe('useLedgerUpcomingFiscalYears', () => {
+ beforeEach(() => {
+ useLedgerCurrentFiscalYear.mockReturnValue({ currentFiscalYear });
+ useOkapiKy.mockReturnValue(kyMock);
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should fetch fiscal years for the ledger', async () => {
+ const { result } = renderHook(() => useLedgerUpcomingFiscalYears('ledgerId'), { wrapper });
+
+ await waitFor(() => expect(result.current.isLoading).toBeFalsy());
+
+ expect(kyMock.get).toHaveBeenCalledWith(FISCAL_YEARS_API, {
+ searchParams: expect.objectContaining({
+ query: `series==${currentFiscalYear.series} and periodStart >= ${currentFiscalYear.periodStart} sortby periodStart/sort.descending series/sort.ascending`,
+ }),
+ signal: expect.any(AbortSignal),
+ });
+ expect(result.current.fiscalYears).toEqual(fiscalYears);
+ });
+});
diff --git a/src/common/utils/fetchFinanceData.test.js b/src/common/utils/fetchFinanceData.test.js
new file mode 100644
index 00000000..f6fdce55
--- /dev/null
+++ b/src/common/utils/fetchFinanceData.test.js
@@ -0,0 +1,19 @@
+import { FINANCE_DATA_API } from '../const';
+import { fetchFinanceData } from './fetchFinanceData';
+
+const httpClient = {
+ get: jest.fn(() => ({
+ json: jest.fn(() => Promise.resolve({})),
+ })),
+};
+
+describe('fetchFinanceData', () => {
+ it('should fetch finance data by specific query', async () => {
+ const fetcher = fetchFinanceData(httpClient);
+ const options = { query: 'financeDataQuery' };
+
+ await fetcher(options);
+
+ expect(httpClient.get).toHaveBeenCalledWith(FINANCE_DATA_API, options);
+ });
+});