feat: add operator options management for field interfaces

This commit is contained in:
Zeke Zhang 2025-04-16 23:58:45 +08:00
parent 9d430d4378
commit 945c2c686c
5 changed files with 201 additions and 7 deletions

View File

@ -0,0 +1,154 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { vi } from 'vitest';
import { Application, CollectionFieldInterface, CollectionFieldInterfaceManager, DataSourceManager } from '..';
// 创建测试用的字段接口类
class TestFieldInterface extends CollectionFieldInterface {
name = 'test';
group = 'test';
filterable = {
operators: [],
};
}
describe('Field Interface Operator', () => {
describe('CollectionFieldInterface.addOperator', () => {
let fieldInterface: TestFieldInterface;
beforeEach(() => {
// 创建测试环境
const mockCollectionFieldInterfaceManager = {} as CollectionFieldInterfaceManager;
fieldInterface = new TestFieldInterface(mockCollectionFieldInterfaceManager);
});
test('should add a new operator', () => {
const testOperator: any = {
label: 'Test Operator',
value: 'test-op',
};
fieldInterface.addOperator(testOperator);
expect(fieldInterface.filterable.operators).toContainEqual(testOperator);
expect(fieldInterface.filterable.operators.length).toBe(1);
});
test('should not add duplicate operators', () => {
const testOperator: any = {
label: 'Test Operator',
value: 'test-op',
};
// Add the same operator twice
fieldInterface.addOperator(testOperator);
fieldInterface.addOperator(testOperator);
// Ensure it was only added once
expect(fieldInterface.filterable.operators.length).toBe(1);
});
test('should initialize operators array if it does not exist', () => {
// Clear the operators array
// @ts-ignore
fieldInterface.filterable = {};
const testOperator: any = {
label: 'Test Operator',
value: 'test-op',
};
fieldInterface.addOperator(testOperator);
expect(Array.isArray(fieldInterface.filterable.operators)).toBeTruthy();
expect(fieldInterface.filterable.operators).toContainEqual(testOperator);
});
});
describe('CollectionFieldInterfaceManager.addFieldInterfaceOperator', () => {
let manager: CollectionFieldInterfaceManager;
let mockDataSourceManager: any;
let testFieldInterface: TestFieldInterface;
beforeEach(() => {
mockDataSourceManager = {} as DataSourceManager;
testFieldInterface = new TestFieldInterface({} as any);
// Create manager
manager = new CollectionFieldInterfaceManager([], {}, mockDataSourceManager);
// Mock getFieldInterface method
manager.getFieldInterface = vi.fn().mockImplementation((name) => {
if (name === 'test') {
return testFieldInterface;
}
return null;
});
});
test('should add operator to specified field interface', () => {
const testOperator: any = {
label: 'Test Operator',
value: 'test-op',
};
// Add operator to the test field interface
manager.addFieldInterfaceOperator('test', testOperator);
// Verify the operator was added correctly
expect(testFieldInterface.filterable.operators).toContainEqual(testOperator);
});
test('should not throw error when field interface does not exist', () => {
const testOperator: any = {
label: 'Test Operator',
value: 'test-op',
};
// Should not throw an exception
expect(() => {
manager.addFieldInterfaceOperator('non-exist', testOperator);
}).not.toThrow();
});
});
describe('Application.addFieldInterfaceOperator', () => {
let app: Application;
let mockDataSourceManager: any;
beforeEach(() => {
mockDataSourceManager = {
collectionFieldInterfaceManager: {
addFieldInterfaceOperator: vi.fn(),
},
};
// Create application instance
app = new Application({ dataSourceManager: mockDataSourceManager });
// Replace the dataSourceManager in the application
app.dataSourceManager = mockDataSourceManager as any;
});
test('should delegate call to dataSourceManager.collectionFieldInterfaceManager', () => {
const testOperator: any = {
label: 'Test Operator',
value: 'test-op',
};
app.addFieldInterfaceOperator('test', testOperator);
// Verify the method was called correctly
expect(mockDataSourceManager.collectionFieldInterfaceManager.addFieldInterfaceOperator).toHaveBeenCalledWith(
'test',
testOperator,
);
});
});
});

View File

@ -495,6 +495,23 @@ export class Application {
);
}
/**
*
*
* @param name
* @param operatorOption
*
* @example
* // 为"单行文本"类型字段添加"等于任意一个"操作符
* app.addFieldInterfaceOperator('input', {
* label: '{{t("equals any of")}}',
* value: '$in',
* });
*/
addFieldInterfaceOperator(name: string, operatorOption: any) {
return this.dataSourceManager.collectionFieldInterfaceManager.addFieldInterfaceOperator(name, operatorOption);
}
addGlobalVar(key: string, value: any, varCtx?: any) {
set(this.globalVars, key, value);
if (varCtx) {

View File

@ -8,7 +8,7 @@
*/
import type { ISchema } from '@formily/react';
import { cloneDeep, capitalize } from 'lodash';
import { cloneDeep, capitalize, set } from 'lodash';
import type { CollectionFieldOptions } from '../collection';
import { CollectionFieldInterfaceManager } from './CollectionFieldInterfaceManager';
import { defaultProps } from '../../collection-manager/interfaces/properties';
@ -161,4 +161,14 @@ export abstract class CollectionFieldInterface {
},
};
}
addOperator(operatorOption: any) {
set(this, 'filterable.operators', this.filterable.operators || []);
if (this.filterable.operators.find((item) => item.value === operatorOption.value)) {
return;
}
this.filterable.operators.push(operatorOption);
}
}

View File

@ -61,6 +61,24 @@ export class CollectionFieldInterfaceManager {
fieldInterface.addComponentOption(componentOption);
}
/**
*
*
* @param name
* @param operatorOption
*
* @example
* // 为"单行文本"类型字段添加"等于任意一个"操作符
* fieldInterfaceManager.addFieldInterfaceOperator('input', {
* label: '{{t("equals any of")}}',
* value: '$in',
* });
*/
addFieldInterfaceOperator(name: string, operatorOption: any) {
const fieldInterface = this.getFieldInterface(name);
fieldInterface?.addOperator(operatorOption);
}
getFieldInterface<T extends CollectionFieldInterface>(name: string) {
return this.collectionFieldInterfaceInstances[name] as T;
}

View File

@ -18,12 +18,7 @@ export class PluginFilterOperatorMultipleKeywordsClient extends Plugin {
// You can get and modify the app instance here
async load() {
console.log(this.app);
// this.app.addComponents({})
// this.app.addScopes({})
// this.app.addProvider()
// this.app.addProviders()
// this.app.router.add()
}
}