Files
n8n-enterprise-unlocked/packages/frontend/editor-ui/src/stores/folders.store.test.ts
Jaakko Husso e860dd6d2e feat(editor): Combine 'Move to Folder' and 'Change owner' modals (#15756)
This new modal also allows transferring entire folders to other projects and users.
2025-05-28 23:41:07 +03:00

209 lines
5.5 KiB
TypeScript

import { vi } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { faker } from '@faker-js/faker';
import { useFoldersStore } from '@/stores/folders.store';
import * as workflowsApi from '@/api/workflows';
import type { ChangeLocationSearchResponseItem, IUsedCredential } from '@/Interface';
import { useRootStore } from '@n8n/stores/useRootStore';
vi.mock('@/utils/apiUtils', () => ({
makeRestApiRequest: vi.fn(),
}));
const createFolder = (
overrides: Partial<ChangeLocationSearchResponseItem> = {},
): ChangeLocationSearchResponseItem => ({
createdAt: faker.date.recent().toISOString(),
updatedAt: faker.date.recent().toISOString(),
id: faker.string.alphanumeric(10),
name: faker.lorem.words(3),
tags: [],
parentFolder: {
id: faker.string.alphanumeric(10),
name: faker.lorem.words(2),
parentFolderId: null,
},
workflowCount: 2,
subFolderCount: 2,
path: [faker.lorem.word(), faker.lorem.word()],
...overrides,
});
describe('folders.store', () => {
let foldersStore: ReturnType<typeof useFoldersStore>;
let rootStore: ReturnType<typeof useRootStore>;
beforeEach(() => {
setActivePinia(createPinia());
rootStore = useRootStore();
foldersStore = useFoldersStore();
});
describe('fetchFoldersAvailableForMove', () => {
const projectId = faker.string.alphanumeric(10);
const folderId = faker.string.alphanumeric(10);
const selectFields = [
'id',
'name',
'createdAt',
'updatedAt',
'project',
'tags',
'parentFolder',
'workflowCount',
'subFolderCount',
'path',
];
it('should fetch folders in a single page, empty filter', async () => {
const folder = createFolder();
vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({
count: 1,
data: [folder],
});
const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId);
expect(available).toEqual([{ ...folder, resource: 'folder' }]);
expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(1);
expect(workflowsApi.getProjectFolders).toHaveBeenCalledWith(
rootStore.restApiContext,
projectId,
{
skip: 0,
take: 100,
sortBy: 'updatedAt:desc',
},
{
excludeFolderIdAndDescendants: folderId,
},
selectFields,
);
});
it('should fetch folders in a single page with filter', async () => {
const folder = createFolder({ name: 'Test Folder' });
vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({
count: 1,
data: [folder],
});
const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId, {
name: 'Test',
});
expect(available).toEqual([{ ...folder, resource: 'folder' }]);
expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(1);
expect(workflowsApi.getProjectFolders).toHaveBeenCalledWith(
rootStore.restApiContext,
projectId,
{
skip: 0,
take: 100,
sortBy: 'updatedAt:desc',
},
{
excludeFolderIdAndDescendants: folderId,
name: 'Test',
},
selectFields,
);
});
it('should fetch folders in multiple pages', async () => {
const folders = Array.from({ length: 150 }, (_, i) =>
createFolder({ name: `Folder ${i + 1}` }),
);
vi.spyOn(workflowsApi, 'getProjectFolders')
.mockResolvedValueOnce({
count: 150,
data: folders.slice(0, 100),
})
.mockResolvedValueOnce({
count: 150,
data: folders.slice(100),
});
const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId, {
name: 'Test',
});
expect(available).toHaveLength(150);
expect(workflowsApi.getProjectFolders).toHaveBeenCalledTimes(2);
expect(workflowsApi.getProjectFolders).toHaveBeenNthCalledWith(
1,
rootStore.restApiContext,
projectId,
{
skip: 0,
take: 100,
sortBy: 'updatedAt:desc',
},
{
excludeFolderIdAndDescendants: folderId,
name: 'Test',
},
selectFields,
);
expect(workflowsApi.getProjectFolders).toHaveBeenNthCalledWith(
2,
rootStore.restApiContext,
projectId,
{
skip: 100,
take: 100,
sortBy: 'updatedAt:desc',
},
{
excludeFolderIdAndDescendants: folderId,
name: 'Test',
},
selectFields,
);
});
it('should cache the results on breadcrumbs cache', async () => {
const folder = createFolder();
vi.spyOn(workflowsApi, 'getProjectFolders').mockResolvedValue({
count: 1,
data: [folder],
});
const available = await foldersStore.fetchFoldersAvailableForMove(projectId, folderId);
expect(available).toEqual([{ ...folder, resource: 'folder' }]);
expect(foldersStore.breadcrumbsCache).toEqual({
[folder.id]: {
id: folder.id,
name: folder.name,
parentFolder: folder.parentFolder?.id,
},
});
});
});
describe('fetchFolderUsedCredentials', () => {
const projectId = faker.string.alphanumeric(10);
const folderId = faker.string.alphanumeric(10);
it('should fetch credentials used within a folder', async () => {
const usedCredential: IUsedCredential = {
id: faker.string.alphanumeric(10),
name: faker.lorem.words(2),
credentialType: faker.lorem.word(),
currentUserHasAccess: true,
sharedWithProjects: [],
};
vi.spyOn(workflowsApi, 'getFolderUsedCredentials').mockResolvedValue([usedCredential]);
const credentials = await foldersStore.fetchFolderUsedCredentials(projectId, folderId);
expect(credentials).toEqual([usedCredential]);
expect(workflowsApi.getFolderUsedCredentials).toHaveBeenCalledWith(
rootStore.restApiContext,
projectId,
folderId,
);
});
});
});