Files
alighasami 3d5eaf9445
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
first commit
2026-03-17 16:22:57 +03:30

316 lines
9.4 KiB
TypeScript

import type { IDataTableProjectService, IExecuteFunctions, INode } from 'n8n-workflow';
import { ANY_CONDITION } from '../../common/constants';
import { DATA_TABLE_ID_FIELD } from '../../common/fields';
import * as getOperation from '../../actions/row/get.operation';
describe('DataTable Get Operation - Sort Feature', () => {
let mockExecuteFunctions: IExecuteFunctions;
let mockDataTableProxy: IDataTableProjectService;
const node = { id: 'test', typeVersion: 1.1 } as INode;
beforeEach(() => {
const getManyRowsAndCount = jest.fn();
const getColumns = jest.fn();
mockDataTableProxy = {
getManyRowsAndCount,
getColumns,
} as unknown as IDataTableProjectService;
getColumns.mockResolvedValue([
{ name: 'id', type: 'number' },
{ name: 'name', type: 'string' },
{ name: 'age', type: 'number' },
{ name: 'status', type: 'string' },
]);
mockExecuteFunctions = {
getNode: jest.fn().mockReturnValue(node),
getNodeParameter: jest.fn(),
helpers: {
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
},
} as unknown as IExecuteFunctions;
jest.clearAllMocks();
});
describe('Single Column Sort', () => {
it('should sort by column ascending', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
if (param === 'orderByDirection') return 'ASC';
if (param === 'returnAll') return false;
if (param === 'limit') return 10;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
count: 2,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: ['name', 'ASC'],
}),
);
});
it('should sort by column descending', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'age';
if (param === 'orderByDirection') return 'DESC';
if (param === 'returnAll') return false;
if (param === 'limit') return 10;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [
{ id: 2, age: 30 },
{ id: 1, age: 25 },
],
count: 2,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: ['age', 'DESC'],
}),
);
});
it('should sort by id column', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'id';
if (param === 'orderByDirection') return 'ASC';
if (param === 'returnAll') return true;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
],
count: 3,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: ['id', 'ASC'],
}),
);
});
});
describe('No Sort Rule', () => {
it('should work without sort rule (orderBy false)', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return false;
if (param === 'returnAll') return false;
if (param === 'limit') return 10;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [{ id: 1 }],
count: 1,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: undefined,
}),
);
});
it('should work with v1.0 (legacy version)', async () => {
// ARRANGE
const v10Node = { id: 'test', typeVersion: 1.0 } as INode;
(mockExecuteFunctions.getNode as jest.Mock).mockReturnValue(v10Node);
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return false;
if (param === 'returnAll') return false;
if (param === 'limit') return 10;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [{ id: 1 }],
count: 1,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: undefined,
}),
);
});
});
describe('Sort with Filters', () => {
it('should combine sort and filters correctly', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
if (param === 'orderByDirection') return 'ASC';
if (param === 'returnAll') return false;
if (param === 'limit') return 10;
if (param === 'filters.conditions') {
return [{ keyName: 'status', condition: 'eq', keyValue: 'active' }];
}
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice', status: 'active' },
{ id: 2, name: 'Bob', status: 'active' },
],
count: 2,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: ['name', 'ASC'],
filter: expect.objectContaining({
type: 'or',
filters: [
{
columnName: 'status',
condition: 'eq',
value: 'active',
},
],
}),
}),
);
});
});
describe('Sort with Pagination', () => {
it('should maintain sort order with returnAll=true', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'id';
if (param === 'orderByDirection') return 'DESC';
if (param === 'returnAll') return true;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [{ id: 5 }, { id: 4 }, { id: 3 }],
count: 3,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: ['id', 'DESC'],
}),
);
});
it('should maintain sort order with limit', async () => {
// ARRANGE
(mockExecuteFunctions.getNodeParameter as jest.Mock).mockImplementation((param) => {
if (param === DATA_TABLE_ID_FIELD) return { mode: 'id', value: 'table123' };
if (param === 'orderBy') return true;
if (param === 'orderByColumn') return 'name';
if (param === 'orderByDirection') return 'ASC';
if (param === 'returnAll') return false;
if (param === 'limit') return 5;
if (param === 'filters.conditions') return [];
if (param === 'matchType') return ANY_CONDITION;
return undefined;
});
(mockDataTableProxy.getManyRowsAndCount as jest.Mock).mockResolvedValue({
data: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
{ id: 4, name: 'David' },
{ id: 5, name: 'Eve' },
],
count: 10,
});
// ACT
await getOperation.execute.call(mockExecuteFunctions, 0);
// ASSERT
expect(mockDataTableProxy.getManyRowsAndCount).toHaveBeenCalledWith(
expect.objectContaining({
sortBy: ['name', 'ASC'],
take: 5,
}),
);
});
});
});