mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
Adds soft‑deletion support for workflows through a new boolean column `isArchived`. When a workflow is archived we now set `isArchived` flag to true and the workflows stays in the database and is omitted from the default workflow listing query. Archived workflows can be viewed in read-only mode, but they cannot be activated. Archived workflows are still available by ID and can be invoked as sub-executions, so existing Execute Workflow nodes continue to work. Execution engine doesn't care about isArchived flag. Users can restore workflows via Unarchive action at the UI.
151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
import { createComponentRenderer } from '@/__tests__/render';
|
|
import userEvent from '@testing-library/user-event';
|
|
import FolderCard from './FolderCard.vue';
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import type { FolderResource } from '../layouts/ResourcesListLayout.vue';
|
|
import type { FolderPathItem, UserAction } from '@/Interface';
|
|
|
|
vi.mock('vue-router', () => {
|
|
const push = vi.fn();
|
|
const resolve = vi.fn().mockReturnValue({ href: '/projects/1/folders/1' });
|
|
return {
|
|
useRouter: vi.fn().mockReturnValue({
|
|
push,
|
|
resolve,
|
|
}),
|
|
useRoute: vi.fn().mockReturnValue({
|
|
params: {
|
|
projectId: '1',
|
|
|
|
folderId: '1',
|
|
},
|
|
query: {},
|
|
}),
|
|
RouterLink: vi.fn(),
|
|
};
|
|
});
|
|
|
|
const DEFAULT_FOLDER: FolderResource = {
|
|
id: '1',
|
|
name: 'Folder 1',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
resourceType: 'folder',
|
|
workflowCount: 2,
|
|
subFolderCount: 2,
|
|
homeProject: {
|
|
id: '1',
|
|
name: 'Project 1',
|
|
icon: null,
|
|
type: 'personal',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
},
|
|
} as const satisfies FolderResource;
|
|
|
|
const DEFAULT_BREADCRUMBS: { visibleItems: FolderPathItem[]; hiddenItems: FolderPathItem[] } = {
|
|
visibleItems: [{ id: '1', label: 'Parent 2' }],
|
|
hiddenItems: [{ id: '2', label: 'Parent 1', parentFolder: '1' }],
|
|
};
|
|
|
|
const renderComponent = createComponentRenderer(FolderCard, {
|
|
props: {
|
|
data: DEFAULT_FOLDER,
|
|
actions: [
|
|
{ label: 'Open', value: 'open', disabled: false },
|
|
{ label: 'Delete', value: 'delete', disabled: false },
|
|
] as const satisfies UserAction[],
|
|
breadcrumbs: DEFAULT_BREADCRUMBS,
|
|
},
|
|
global: {
|
|
stubs: {
|
|
'router-link': {
|
|
template: '<div data-test-id="folder-card-link"><slot /></div>',
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
describe('FolderCard', () => {
|
|
let pinia: ReturnType<typeof createPinia>;
|
|
|
|
beforeEach(async () => {
|
|
pinia = createPinia();
|
|
setActivePinia(pinia);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should render folder info correctly', () => {
|
|
const { getByTestId } = renderComponent();
|
|
expect(getByTestId('folder-card-icon')).toBeInTheDocument();
|
|
expect(getByTestId('folder-card-name')).toHaveTextContent(DEFAULT_FOLDER.name);
|
|
expect(getByTestId('folder-card-workflow-count')).toHaveTextContent('2');
|
|
expect(getByTestId('folder-card-folder-count')).toHaveTextContent('2');
|
|
expect(getByTestId('folder-card-last-updated')).toHaveTextContent('Last updated just now');
|
|
expect(getByTestId('folder-card-created')).toHaveTextContent('Created just now');
|
|
});
|
|
|
|
it('should not render workflow & folder count if they are 0', () => {
|
|
const { queryByTestId } = renderComponent({
|
|
props: {
|
|
data: {
|
|
...DEFAULT_FOLDER,
|
|
workflowCount: 0,
|
|
subFolderCount: 0,
|
|
},
|
|
},
|
|
});
|
|
expect(queryByTestId('folder-card-workflow-count')).not.toBeInTheDocument();
|
|
expect(queryByTestId('folder-card-folder-count')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should not render action dropdown if no actions are provided', () => {
|
|
const { queryByTestId } = renderComponent({
|
|
props: {
|
|
actions: [],
|
|
},
|
|
});
|
|
expect(queryByTestId('folder-card-actions')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('should render action dropdown if actions are provided', () => {
|
|
const { getByTestId } = renderComponent();
|
|
expect(getByTestId('folder-card-actions')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should emit action event when action is clicked', async () => {
|
|
const { getByTestId, emitted } = renderComponent();
|
|
const actionButton = getByTestId('folder-card-actions').querySelector('[role=button]');
|
|
if (!actionButton) {
|
|
throw new Error('Action button not found');
|
|
}
|
|
await userEvent.click(actionButton);
|
|
const actionToggleId = actionButton.getAttribute('aria-controls');
|
|
const actionDropdown = document.getElementById(actionToggleId as string) as HTMLElement;
|
|
expect(actionDropdown).toBeInTheDocument();
|
|
const deleteAction = getByTestId('action-delete');
|
|
expect(deleteAction).toBeInTheDocument();
|
|
await userEvent.click(deleteAction);
|
|
expect(emitted('action')).toEqual([[{ action: 'delete', folderId: '1' }]]);
|
|
});
|
|
|
|
it('should emit folder-open action', async () => {
|
|
const { getByTestId, emitted } = renderComponent();
|
|
const actionButton = getByTestId('folder-card-actions').querySelector('[role=button]');
|
|
if (!actionButton) {
|
|
throw new Error('Action button not found');
|
|
}
|
|
await userEvent.click(actionButton);
|
|
const actionToggleId = actionButton.getAttribute('aria-controls');
|
|
const actionDropdown = document.getElementById(actionToggleId as string) as HTMLElement;
|
|
expect(actionDropdown).toBeInTheDocument();
|
|
const deleteAction = getByTestId('action-open');
|
|
expect(deleteAction).toBeInTheDocument();
|
|
await userEvent.click(deleteAction);
|
|
expect(emitted('folderOpened')).toEqual([[{ folder: DEFAULT_FOLDER }]]);
|
|
});
|
|
});
|