From 44e7f48f99ed3057adab1e9ce52663138a38235b Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Thu, 28 Mar 2024 21:04:53 +0800 Subject: [PATCH] test: main data source e2e test (#3816) * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: main data source e2e test * test: test * test: test * refactor: code improve * test: test --- .../src/collection-manager/interfaces/uuid.ts | 2 +- packages/core/test/src/e2e/e2eUtils.ts | 92 ++- .../collection-template/general.test.ts | 662 ++++++++++++++++++ .../CollectionFieldInterfaceSelect.tsx | 3 + .../components/FieldTitleInput.tsx | 4 +- .../components/FieldType.tsx | 3 + .../ViewDatabaseConnectionAction.tsx | 2 + .../__tests__/data-source-with-acl.test.ts | 27 + .../server/resourcers/data-sources-roles.ts | 19 + .../plugin-mock-collections/package.json | 3 + .../src/server/external.sql | 175 +++++ .../src/server/index.ts | 40 +- 12 files changed, 1023 insertions(+), 9 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-mock-collections/src/server/external.sql diff --git a/packages/core/client/src/collection-manager/interfaces/uuid.ts b/packages/core/client/src/collection-manager/interfaces/uuid.ts index a934d06d3..7dbfc0dd7 100644 --- a/packages/core/client/src/collection-manager/interfaces/uuid.ts +++ b/packages/core/client/src/collection-manager/interfaces/uuid.ts @@ -17,7 +17,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface { 'x-validator': 'uuid', }, }; - availableTypes = ['string', 'uid']; + availableTypes = ['string', 'uid', 'uuid']; properties = { 'uiSchema.title': { type: 'string', diff --git a/packages/core/test/src/e2e/e2eUtils.ts b/packages/core/test/src/e2e/e2eUtils.ts index bf948d5fe..5f21adeb1 100644 --- a/packages/core/test/src/e2e/e2eUtils.ts +++ b/packages/core/test/src/e2e/e2eUtils.ts @@ -53,7 +53,6 @@ export interface CollectionSetting { * @default false */ inherit?: boolean; - inherits?: string[]; category?: any[]; hidden?: boolean; description?: string; @@ -120,6 +119,23 @@ interface AclRoleSetting { key?: string; //菜单权限配置 menuUiSchemas?: string[]; + dataSourceKey?: string; +} + +interface DatabaseSetting { + database: string; + host: string; + port: string; + schema?: string; + username?: string; + password?: string; +} +interface DataSourceSetting { + key: string; + displayName: string; + type: string; + options: DatabaseSetting; + enabled?: boolean; } export interface PageConfig { @@ -229,6 +245,15 @@ interface ExtendUtils { * 更新角色权限配置 */ updateRole: (roleSetting: AclRoleSetting) => Promise; + /** + * 创建一个外部数据源(pg) + */ + mockExternalDataSource: (DataSourceSetting: DataSourceSetting) => Promise; + /** + * 删除外部数据源 + * @param key 外部数据源key + */ + destoryExternalDataSource: (key: string) => Promise; } const PORT = process.env.APP_PORT || 20000; @@ -437,6 +462,20 @@ const _test = base.extend({ await use(updateRole); }, + mockExternalDataSource: async ({ browser }, use) => { + const mockExternalDataSource = async (DataSourceSetting: DataSourceSetting) => { + return createExternalDataSource(DataSourceSetting); + }; + + await use(mockExternalDataSource); + }, + destoryExternalDataSource: async ({ browser }, use) => { + const destoryDataSource = async (key: string) => { + return destoryExternalDataSource(key); + }; + + await use(destoryDataSource); + }, }); export const test = Object.assign(_test, { @@ -581,10 +620,6 @@ const deleteCollections = async (collectionNames: string[]) => { const result = await api.post(`/api/collections:destroy?${params}`, { headers, - params: { - // 自动删除依赖于集合的对象(如视图),进而删除依赖于这些对象的所有对象 - cascade: true, - }, }); if (!result.ok()) { @@ -696,8 +731,12 @@ const updateRole = async (roleSetting: AclRoleSetting) => { const state = await api.storageState(); const headers = getHeaders(state); const name = roleSetting.name; + const dataSourceKey = roleSetting.dataSourceKey; - const result = await api.post(`/api/roles:update?filterByTk=${name}`, { + const url = !dataSourceKey + ? `/api/roles:update?filterByTk=${name}` + : `/api/dataSources/${dataSourceKey}/roles:update?filterByTk=${name}`; + const result = await api.post(url, { headers, data: { ...roleSetting }, }); @@ -724,7 +763,48 @@ const setDefaultRole = async (name) => { data: { roleName: name }, }); }; +/** + * 创建外部数据源 + * @paramn + */ +const createExternalDataSource = async (dataSourceSetting: DataSourceSetting) => { + const api = await request.newContext({ + storageState: process.env.PLAYWRIGHT_AUTH_FILE, + }); + const state = await api.storageState(); + const headers = getHeaders(state); + const result = await api.post(`/api/dataSources:create`, { + headers, + data: { ...dataSourceSetting }, + }); + if (!result.ok()) { + throw new Error(await result.text()); + } + const dataSourceData = (await result.json()).data; + return dataSourceData; +}; + +/** + * 删除外部数据源 + * @paramn + */ +const destoryExternalDataSource = async (key) => { + const api = await request.newContext({ + storageState: process.env.PLAYWRIGHT_AUTH_FILE, + }); + const state = await api.storageState(); + const headers = getHeaders(state); + const result = await api.post(`/api/dataSources:destroy?filterByTk=${key}`, { + headers, + }); + + if (!result.ok()) { + throw new Error(await result.text()); + } + const dataSourceData = (await result.json()).data; + return dataSourceData; +}; /** * 根据 collection 的配置生成 Faker 数据 * @param collectionSetting diff --git a/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/collection-template/general.test.ts b/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/collection-template/general.test.ts index 2d2e29d9d..f02855456 100644 --- a/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/collection-template/general.test.ts +++ b/packages/plugins/@nocobase/plugin-collection-manager/src/client/__e2e__/collection-template/general.test.ts @@ -97,6 +97,238 @@ test.describe('create collection', () => { }); }); +// //预设字段 +test.describe('create collection with preset fields', () => { + test('all preset fields by default', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //默认提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: true, + createdAt: true, + createdBy: true, + updatedAt: true, + updatedBy: true, + fields: expect.arrayContaining([ + expect.objectContaining({ + name: 'id', + type: 'bigInt', + // 其他属性 + }), + expect.objectContaining({ + name: 'createdAt', + type: 'date', + // 其他属性 + }), + expect.objectContaining({ + name: 'createdBy', + type: 'belongsTo', + // 其他属性 + }), + expect.objectContaining({ + name: 'updatedBy', + type: 'belongsTo', + // 其他属性 + }), + expect.objectContaining({ + name: 'updatedAt', + type: 'date', + // 其他属性 + }), + ]), + }); + }); + + test('id preset field', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover(); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click(); + await page.getByRole('row', { name: 'ID Integer Primary key' }).locator('.ant-checkbox-input').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: true, + createdAt: false, + createdBy: false, + updatedAt: false, + updatedBy: false, + fields: expect.arrayContaining([ + expect.objectContaining({ + name: 'id', + type: 'bigInt', + // 其他属性 + }), + ]), + }); + }); + test('createdAt preset field', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover(); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click(); + await page.getByRole('row', { name: 'Created at' }).locator('.ant-checkbox-input').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: false, + createdAt: true, + createdBy: false, + updatedAt: false, + updatedBy: false, + fields: expect.arrayContaining([ + expect.objectContaining({ + name: 'createdAt', + type: 'date', + // 其他属性 + }), + ]), + }); + }); + test('createdBy preset field', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover(); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click(); + await page.getByRole('row', { name: 'created By' }).locator('.ant-checkbox-input').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: false, + createdAt: false, + createdBy: true, + updatedAt: false, + updatedBy: false, + fields: expect.arrayContaining([ + expect.objectContaining({ + name: 'createdBy', + type: 'belongsTo', + // 其他属性 + }), + ]), + }); + }); + test('updatedBy preset field', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover(); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click(); + await page.getByRole('row', { name: 'Last updated by' }).locator('.ant-checkbox-input').check(); + //添加关系字段,断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: false, + createdAt: false, + createdBy: false, + updatedAt: false, + updatedBy: true, + fields: expect.arrayContaining([ + expect.objectContaining({ + name: 'updatedBy', + type: 'belongsTo', + // 其他属性 + }), + ]), + }); + }); + test('updatedAt preset field', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover(); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click(); + await page.getByRole('row', { name: 'Last updated at' }).locator('.ant-checkbox-input').check(); + //添加关系字段,断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: false, + createdAt: false, + createdBy: false, + updatedAt: true, + updatedBy: false, + fields: expect.arrayContaining([ + expect.objectContaining({ + name: 'updatedAt', + type: 'date', + // 其他属性 + }), + ]), + }); + }); + test('unselect preset fields', async ({ page }) => { + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByRole('button', { name: 'plus Create collection down' }).click(); + await page.getByRole('menuitem', { name: 'General collection' }).locator('span').click(); + await page.getByLabel('block-item-Input-collections-Collection display name').getByRole('textbox').fill(uid()); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().hover(); + await page.locator('.ant-drawer-content-wrapper .ant-table-container .ant-table-selection-column').first().click(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('api/collections:create')), + page.getByLabel('action-Action-Submit').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + autoGenId: false, + createdAt: false, + createdBy: false, + updatedAt: false, + updatedBy: false, + fields: [], + }); + }); +}); + test.describe('configure fields', () => { test('basic', async ({ page, mockCollections }) => { test.slow(); @@ -261,6 +493,148 @@ test.describe('configure fields', () => { }); }); +//创建非Id为主键/唯一索引 +test.describe('create primary key or unique index other than ID.', () => { + test('integer field as primary key', async ({ page, mockCollection }) => { + const name = uid(); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await mockCollection({ + name: name, + autoGenId: false, + fields: [], + }); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(name); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'Integer' }).click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid()); + await page.getByLabel('Primary').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('/fields:create')), + page.getByLabel('action-Action-Submit-fields-').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + unique: false, + interface: 'integer', + primaryKey: true, + type: 'bigInt', + }); + }); + test('integer field set unique index', async ({ page, mockCollection }) => { + const name = uid(); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await mockCollection({ + name: name, + autoGenId: false, + fields: [], + }); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(name); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'Integer' }).click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid()); + // Primary 和 Unique选中一个,其中一个自动取消勾选 + await page.getByLabel('Primary').check(); + await page.getByLabel('Unique').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('/fields:create')), + page.getByLabel('action-Action-Submit-fields-').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + unique: true, + interface: 'integer', + primaryKey: false, + type: 'bigInt', + }); + }); + test('input(string) field set unique index', async ({ page, mockCollection }) => { + const name = uid(); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await mockCollection({ + name: name, + autoGenId: false, + fields: [], + }); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(name); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'Single line text' }).click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid()); + // Primary 和 Unique选中一个,其中一个自动取消勾选 + await page.getByLabel('Primary').check(); + await page.getByLabel('Unique').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('/fields:create')), + page.getByLabel('action-Action-Submit-fields-').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + unique: true, + interface: 'input', + primaryKey: false, + type: 'string', + }); + }); + test('input(string) field set as primary key', async ({ page, mockCollection }) => { + const name = uid(); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await mockCollection({ + name: name, + autoGenId: false, + fields: [], + }); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(name); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${name}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'Single line text' }).click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').click(); + await page.getByLabel('block-item-Input-fields-Field display name').getByRole('textbox').fill(uid()); + // Primary 和 Unique选中一个,其中一个自动取消勾选 + await page.getByLabel('Unique').check(); + await page.getByLabel('Primary').check(); + //断言提交的data是否符合预期 + const [request] = await Promise.all([ + page.waitForRequest((request) => request.url().includes('/fields:create')), + page.getByLabel('action-Action-Submit-fields-').click(), + ]); + const postData = request.postDataJSON(); + //提交的数据符合预期 + expect(postData).toMatchObject({ + unique: false, + interface: 'input', + primaryKey: true, + type: 'string', + }); + }); +}); + test.describe('edit', () => { test('basic', async ({ page, mockCollection }) => { const collectionDisplayName = uid(); @@ -283,3 +657,291 @@ test.describe('edit', () => { await expect(page.getByRole('cell', { name: newDescription, exact: true })).toBeVisible(); }); }); + +//创建关系字段 +//sourceKey, targetKey, optional field types are [string ',' bigInt ',' integer ',' uuid ',' uid '] and are non primary key field with a Unique index set +test.describe('association constraints support selecting non-primary key fields with Unique indexes', () => { + const fields = [ + { name: 'id', interface: 'id', type: 'bigInt' }, + { name: 'string', interface: 'input', type: 'string', unique: true }, + { name: 'bigInt', type: 'bigInt', interface: 'integer', unique: true }, + { name: 'integer', type: 'integer', interface: 'input', unique: true }, + { name: 'uuid', type: 'uuid', interface: 'input', unique: true }, + { name: 'uid', type: 'uid', interface: 'input', unique: true }, + { name: 'string1', interface: 'input', type: 'string' }, + { name: 'bigInt1', type: 'bigInt', interface: 'integer' }, + { name: 'integer1', type: 'integer', interface: 'input' }, + { name: 'uuid1', type: 'uuid', interface: 'input' }, + { name: 'uid1', type: 'uid', interface: 'input' }, + ]; + test('oho sourceKey', async ({ page, mockCollection }) => { + const collectionName = uid(); + await mockCollection({ + name: collectionName, + autoGenId: true, + fields, + }); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(collectionName); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'One to one (has one)' }).click(); + await page.getByLabel('block-item-SourceKey-fields-').click(); + // 获取所有选项的文本内容 + const options = await page.locator('.rc-virtual-list').evaluate(() => { + const optionElements = document.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期 + expect(options).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid'])); + }); + test('obo targetKey', async ({ page, mockCollection }) => { + const collectionName = uid(); + await mockCollection({ + name: collectionName, + autoGenId: true, + fields, + }); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(collectionName); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'One to one (belongs to)' }).click(); + await page.getByLabel('block-item-Select-fields-Target collection').click(); + await page.getByRole('option', { name: collectionName }).locator('div').click(); + await page.getByLabel('block-item-TargetKey-fields-').click(); + // 获取所有选项的文本内容 + const options = await page + .locator('.rc-virtual-list') + .nth(1) + .evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期 + expect(options).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid'])); + }); + test('o2m targetKey & sourceKey', async ({ page, mockCollection }) => { + const collectionName = uid(); + + await mockCollection({ + name: collectionName, + autoGenId: true, + fields, + }); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(collectionName); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'One to many' }).click(); + + await page.getByLabel('block-item-SourceKey-fields-').click(); + // sourceKey 选项符合预期 + const sourcekeyOptions = await page.locator('.rc-virtual-list').evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期 + expect(sourcekeyOptions).toEqual(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']); + + await page.getByLabel('block-item-Select-fields-Target collection').click(); + await page.getByRole('option', { name: collectionName }).locator('div').click(); + await page.getByLabel('block-item-TargetKey-fields-').click(); + + //targetKey 选项符合预期 + const targetKeyOptions = await page + .locator('.rc-virtual-list') + .nth(2) + .evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期,o2m的targetkey 不限制unique + expect(targetKeyOptions).toEqual( + expect.arrayContaining([ + 'ID', + 'string', + 'bigInt', + 'integer', + 'uuid', + 'uid', + 'string1', + 'bigInt1', + 'integer1', + 'uuid1', + 'uid1', + ]), + ); + }); + + test('m2o targetKey & foreignKey', async ({ page, mockCollection }) => { + const collectionName = uid(); + const foreignKey = `f_${uid()}`; + await mockCollection({ + name: collectionName, + autoGenId: true, + fields, + }); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(collectionName); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'Many to one' }).click(); + + await page.getByLabel('block-item-ForeignKey-fields-').locator('input').click(); + // ForeignKey 选项符合预期 + const foreignKeyOptions = await page.locator('.rc-virtual-list').evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期 + expect(foreignKeyOptions).toEqual( + expect.arrayContaining([ + 'ID', + 'string', + 'bigInt', + 'integer', + 'uuid', + 'uid', + 'string1', + 'bigInt1', + 'integer1', + 'uuid1', + 'uid1', + ]), + ); + //ForeignKey 支持自定义输入和选择 + await page.getByLabel('block-item-ForeignKey-fields-').locator('input').clear(); + await page.getByLabel('block-item-ForeignKey-fields-').locator('input').fill(foreignKey); + + const inputValue = await page.getByLabel('block-item-ForeignKey-fields-').locator('input').inputValue(); + expect(inputValue).toEqual(foreignKey); + await page.getByRole('option', { name: 'uuid1' }).locator('div').click(); + const optionValue = await page.getByLabel('block-item-ForeignKey-fields-').locator('input').inputValue(); + expect(optionValue).toEqual('uuid1'); + + await page.getByLabel('block-item-Select-fields-Target collection').click(); + await page.getByRole('option', { name: collectionName }).locator('div').click(); + await page.getByLabel('block-item-TargetKey-fields-').click(); + + //targetKey 选项符合预期 + const targetKeyOptions = await page + .locator('.rc-virtual-list') + .nth(2) + .evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期, 限制unique + expect(targetKeyOptions).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid'])); + }); + + test('m2m sourceKey & foreignKey & targetKey', async ({ page, mockCollection }) => { + const collectionName = uid(); + const foreignKey = `f_${uid()}`; + await mockCollection({ + name: collectionName, + autoGenId: true, + fields, + }); + await page.goto('/admin/settings/data-source-manager/list'); + await page.getByRole('button', { name: 'Configure' }).first().click(); + await page.getByLabel('action-Filter.Action-Filter-').click(); + await page.getByRole('textbox').nth(1).click(); + await page.getByRole('textbox').nth(1).fill(collectionName); + await page.getByRole('button', { name: 'Submit' }).click(); + await page.getByLabel(`action-Action.Link-Configure fields-collections-${collectionName}`).click(); + await page.getByRole('button', { name: 'plus Add field' }).click(); + await page.getByRole('menuitem', { name: 'Many to many' }).click(); + + await page.getByLabel('block-item-SourceKey-fields-').click(); + // sourceKey 选项符合预期 + const sourcekeyOptions = await page.locator('.rc-virtual-list').evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + expect(sourcekeyOptions).toEqual(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid']); + + // ForeignKey1 选项符合预期 + await page.getByLabel('block-item-ThroughCollection-').click(); + await page.getByRole('option', { name: collectionName }).locator('div').click(); + await page.getByLabel('block-item-ForeignKey-fields-Foreign key 1').locator('input').click(); + const foreignKeyOptions = await page + .locator('.rc-virtual-list') + .nth(2) + .evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期 + expect(foreignKeyOptions).toEqual( + expect.arrayContaining([ + 'ID', + 'string', + 'bigInt', + 'integer', + 'uuid', + 'uid', + 'string1', + 'bigInt1', + 'integer1', + 'uuid1', + 'uid1', + ]), + ); + //ForeignKey1 支持自定义输入和选择 + await page.getByLabel('block-item-ForeignKey-fields-Foreign key 1').locator('input').clear(); + await page.getByLabel('block-item-ForeignKey-fields-Foreign key 1').locator('input').fill(foreignKey); + + const inputValue = await page + .getByLabel('block-item-ForeignKey-fields-Foreign key 1') + .locator('input') + .inputValue(); + expect(inputValue).toEqual(foreignKey); + await page.getByRole('option', { name: 'uuid1' }).locator('div').click(); + const optionValue = await page + .getByLabel('block-item-ForeignKey-fields-Foreign key 1') + .locator('input') + .inputValue(); + expect(optionValue).toEqual('uuid1'); + + await page.getByLabel('block-item-Select-fields-Target collection').click(); + await page.getByRole('option', { name: collectionName }).locator('div').click(); + await page.getByLabel('block-item-TargetKey-fields-').click(); + + //targetKey 选项符合预期 + const targetKeyOptions = await page + .locator('.rc-virtual-list') + .nth(2) + .evaluate((element) => { + const optionElements = element.querySelectorAll('.ant-select-item-option'); + return Array.from(optionElements).map((option) => option.textContent); + }); + + // 断言下拉列表是否符合预期, 限制unique + expect(targetKeyOptions).toEqual(expect.arrayContaining(['ID', 'string', 'bigInt', 'integer', 'uuid', 'uid'])); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionFieldInterfaceSelect.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionFieldInterfaceSelect.tsx index 745d0aa33..cad9c93b0 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionFieldInterfaceSelect.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/CollectionFieldInterfaceSelect.tsx @@ -38,6 +38,9 @@ export const CollectionFieldInterfaceSelect = observer( ) : ( ; + return ( + + ); }, { displayName: 'FieldTitleInput' }, ); diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/FieldType.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/FieldType.tsx index eb5f4175b..6683f9ee6 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/FieldType.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/components/FieldType.tsx @@ -12,6 +12,9 @@ export const FieldType = observer( {value} ) : (