diff --git a/packages/core/actions/src/__tests__/index.ts b/packages/core/actions/src/__tests__/index.ts index 3c41afd97..5e52ef18e 100644 --- a/packages/core/actions/src/__tests__/index.ts +++ b/packages/core/actions/src/__tests__/index.ts @@ -8,7 +8,6 @@ import supertest, { SuperAgentTest } from 'supertest'; import db2resource from '../../../server/src/middlewares/db2resource'; import { uid } from '@nocobase/utils'; - interface ActionParams { fields?: string[]; filter?: any; @@ -117,7 +116,8 @@ export class MockServer extends Koa { { get(target, method: string, receiver) { return (params: ActionParams = {}) => { - let { filterByTk, values = {}, file, ...restParams } = params; + let { filterByTk } = params; + const { values = {}, file, ...restParams } = params; if (params.associatedIndex) { resourceOf = params.associatedIndex; } diff --git a/packages/core/client/src/__tests__/e2e/action.test.ts b/packages/core/client/src/__tests__/e2e/action.test.ts new file mode 100644 index 000000000..ee5617216 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/action.test.ts @@ -0,0 +1,342 @@ +import { expect, test } from '@nocobase/test/client'; +const tablePageSchema = { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + '3r0yxum84w2': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + vduni5v1u2v: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + qwrauugqc1k: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + '92vs3ej14vo': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-decorator-props': { + collection: 'users', + resource: 'users', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + disableTemplate: false, + }, + 'x-designer': 'TableBlockDesigner', + 'x-component': 'CardItem', + 'x-filter-targets': [], + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'TableActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-uid': 'gzu2uanef12', + 'x-async': false, + 'x-index': 1, + }, + vlkh4fiq98e: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'TableColumnInitializers', + 'x-component': 'TableV2', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableBlockProps }}', + }, + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'TableActionColumnInitializers', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-uid': 'q44b0dv50za', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'i8tnis8ymcm', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'u60i2x1lo97', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '6jb6jfh1770', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9oykdej2ufq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8iolmzoelll', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '0dhkzj75zh7', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'n8gs3wg1sxp', + 'x-async': true, + 'x-index': 1, +}; + +test.describe('add action & remove action', () => { + test('add action & remove action in block', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page.getByLabel('block-item-CardItem-users-table').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').click(); + //添加按钮 + await page.getByLabel('Enable actions-Filter').click(); + await page.getByLabel('Enable actions-Add new').click(); + await page.getByLabel('Enable actions-Delete').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').hover(); + await page.getByText('Enable actions').hover(); + await expect(page.getByLabel('action-Filter.Action-Filter-filter-users-table')).toBeVisible(); + await expect(page.getByLabel('action-Action-Add new-create-users-table')).toBeVisible(); + await expect(page.getByLabel('action-Action-Delete-destroy-users-table')).toBeVisible(); + await expect(page.getByLabel('Enable actions-Filter').getByRole('switch')).toBeChecked(); + await expect(page.getByLabel('Enable actions-Add new').getByRole('switch')).toBeChecked(); + await expect(page.getByLabel('Enable actions-Delete').getByRole('switch')).toBeChecked(); + //移除按钮 + await page.getByLabel('Enable actions-Filter').click(); + await page.getByLabel('Enable actions-Add new').click(); + await page.getByLabel('Enable actions-Delete').click(); + await expect(page.getByLabel('action-Action-Add new-create-users-table')).not.toBeVisible(); + await expect( + page.getByLabel('block-item-CardItem-users-table').getByRole('button', { name: 'Delete' }), + ).not.toBeVisible(); + await expect( + page.getByLabel('block-item-CardItem-users-table').getByLabel('Filter', { exact: true }), + ).not.toBeVisible(); + await expect(page.getByLabel('Enable actions-Filter').getByRole('switch')).not.toBeChecked(); + await expect(page.getByLabel('Enable actions-Add new').getByRole('switch')).not.toBeChecked(); + await expect(page.getByLabel('Enable actions-Delete').getByRole('switch')).not.toBeChecked(); + }); +}); + +test.describe('action drag in block', () => { + test('drag th action orders', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page.getByLabel('block-item-CardItem-users-table').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').click(); + //添加按钮 + await page.getByLabel('Enable actions-Add new').click(); + await page.getByLabel('Enable actions-Delete').click(); + await page.getByLabel('Enable actions-Refresh').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').hover(); + await page.getByText('Enable actions').hover(); + await page.waitForTimeout(1000); // 等待1秒钟 + + const addNewBtn = await page.getByLabel('action-Action-Add new-create-users-table'); + await addNewBtn.hover(); + const addNewDrag = await page.getByLabel('action-Action-Add new-create-users-table').getByLabel('designer-drag'); + await addNewDrag.hover(); + + const delBtn = await page.getByLabel('action-Action-Delete-destroy-users-table'); + await addNewDrag.dragTo(delBtn); + const refreshBtn = await page.getByLabel('action-Action-Refresh-refresh-users-table'); + await addNewBtn.hover(); + await addNewBtn.getByLabel('designer-drag').dragTo(refreshBtn); + + const addNew = await addNewBtn.boundingBox(); + const del = await delBtn.boundingBox(); + const refresh = await refreshBtn.boundingBox(); + const max = Math.max(addNew.x, refresh.x, del.x); + //拖拽调整排序符合预期 + await expect(addNew.x).toBe(max); + }); +}); + +test.describe('action display config', () => { + test('editing action name,icon and color', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page.getByLabel('block-item-CardItem-users-table').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').click(); + //添加按钮 + await page.getByLabel('Enable actions-Add new').click(); + await page.getByLabel('action-Action-Add new-create-users-table').hover(); + await page.getByLabel('action-Action-Add new-create-users-table').getByLabel('designer-schema-settings').hover(); + //更新按钮图标、名称、样式 + await page.getByLabel('Edit button').click(); + await page.getByRole('textbox').fill('Add new1'); + await page.getByRole('button', { name: 'close', exact: true }).click(); + await page.getByRole('button', { name: 'Select icon', exact: true }).click(); + await page.getByLabel('user-add').click(); + await page.getByLabel('Danger red').check(); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + await expect( + await page.getByLabel('block-item-CardItem-users-table').locator('.nb-action-bar').getByLabel('user-add'), + ).toBeVisible(); + await expect( + page.getByLabel('block-item-CardItem-users-table').locator('.nb-action-bar').locator('.ant-btn-dangerous'), + ).toBeVisible(); + }); + test('action open mode ', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page.getByLabel('block-item-CardItem-users-table').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').hover(); + //添加按钮 + await page.getByLabel('Enable actions-Add new').click(); + await page.getByLabel('action-Action-Add new-create-users-table').click(); + await expect(await page.locator('.ant-drawer')).toBeVisible(); + + //更新按钮打开方式 + await page.locator('.ant-drawer-mask').click(); + await page.getByLabel('action-Action-Add new-create-users-table').hover(); + await page.getByRole('button', { name: 'designer-schema-settings-Action-Action.Designer-users' }).hover(); + await page.getByTitle('Open mode').click(); + await page.getByRole('option', { name: 'Dialog' }).click(); + await page.getByLabel('action-Action-Add new-create-users-table').click(); + const drawerComponent = page.getByTestId('modal-Action.Container-users-Add record'); + await expect(drawerComponent).toBeInViewport(); + }); + test('setting action model size', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page.getByLabel('block-item-CardItem-users-table').click(); + await page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users').hover(); + //添加按钮 + await page.getByLabel('Enable actions-Add new').click(); + await page.getByLabel('action-Action-Add new-create-users-table').click(); + await expect(await page.locator('.ant-drawer')).toBeVisible(); + await page.locator('.ant-drawer-mask').click(); + //修改尺寸 + await page.getByLabel('action-Action-Add new-create-users-table').hover(); + await page.getByRole('button', { name: 'designer-schema-settings-Action-Action.Designer-users' }).hover(); + await page.getByTitle('Popup size').click(); + + //默认尺寸为Middle + await expect(await page.getByTitle('Popup size').locator('.ant-select-selection-item').innerText()).toBe('Middle'); + //设置为large + await page.getByRole('option', { name: 'Large' }).click(); + + await page.getByLabel('action-Action-Add new-create-users-table').click(); + const drawerItem = await page.locator('.ant-drawer > .ant-drawer-content-wrapper'); + const drawerWidth = await drawerItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + const parent = element.parentElement; + const parentWidth = window.getComputedStyle(parent).getPropertyValue('width'); + const percentageWidth = (parseFloat(computedStyle.width) / parseFloat(parentWidth)) * 100; + return percentageWidth; + }); + //宽度为70% + await expect(drawerWidth).toBe(70); + }); +}); + +test.describe('action linkage rule', () => { + test('action linkage to hide', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page + .getByLabel('block-item-CardItem-users-table') + .getByRole('button', { name: 'Actions', exact: true }) + .hover(); + await page.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-users').hover(); + await page.getByLabel('Enable actions-View').click(); + await page.getByLabel('block-item-CardItem-users-table').getByLabel('View').hover(); + await page.getByLabel('View').getByLabel('designer-schema-settings').hover(); + await page.getByRole('button', { name: 'Linkage rules' }).click(); + await page.getByRole('button', { name: 'plus Add linkage rule', exact: true }).click(); + await page.getByText('Add condition', { exact: true }).click(); + await page.getByTestId('filter-select-field').click(); + await page.getByTitle('ID', { exact: true }).getByText('ID').click(); + await page.getByRole('spinbutton').fill('1'); + await page.getByText('Add property').click(); + await page.getByTestId('select-linkage-properties').click(); + await page.getByText('Hidden').click(); + await page.locator('.ant-modal').getByRole('button', { name: 'OK' }).click(); + //配置中,按钮显示半透明 + const actionItem = page.getByLabel('block-item-CardItem-users-table').getByLabel('View'); + const inputErrorBorderColor = await actionItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.opacity; + }); + await expect(inputErrorBorderColor).toBe('0.1'); + //使用中,按钮隐藏 + await page.getByRole('button', { name: 'highlight' }).click(); + await expect(page.getByLabel('block-item-CardItem-users-table').getByLabel('View')).not.toBeVisible(); + }); + test('action linkage to disabled', async ({ page, mockPage }) => { + await mockPage({ pageSchema: tablePageSchema }).goto(); + await page + .getByLabel('block-item-CardItem-users-table') + .getByRole('button', { name: 'Actions', exact: true }) + .hover(); + await page.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-users').hover(); + await page.getByLabel('Enable actions-View').click(); + await page.getByLabel('block-item-CardItem-users-table').getByLabel('View').hover(); + await page.getByLabel('View').getByLabel('designer-schema-settings').hover(); + await page.getByRole('button', { name: 'Linkage rules' }).click(); + await page.getByRole('button', { name: 'plus Add linkage rule', exact: true }).click(); + await page.getByText('Add condition', { exact: true }).click(); + await page.getByTestId('filter-select-field').click(); + await page.getByTitle('ID', { exact: true }).getByText('ID').click(); + await page.getByRole('spinbutton').fill('1'); + await page.getByText('Add property').click(); + await page.getByTestId('select-linkage-properties').click(); + await page.getByText('Disabled').click(); + await page.locator('.ant-modal').getByRole('button', { name: 'OK' }).click(); + await page.waitForTimeout(1000); // 等待1秒钟 + const linkBtn = page.getByLabel('block-item-CardItem-users-table').getByLabel('View'); + const linkBtnCursor = await linkBtn.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.cursor; + }); + //按钮禁用 + await expect(linkBtnCursor).toBe('not-allowed'); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/block.test.ts b/packages/core/client/src/__tests__/e2e/block.test.ts new file mode 100644 index 000000000..28abbae63 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/block.test.ts @@ -0,0 +1,561 @@ +import { expect, test } from '@nocobase/test/client'; + +const tablePageSchema = { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + mwxaaxb9y9v: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + ibb0kjq3kyl: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + p39cigcjpij: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + '237ec1x538e': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-decorator-props': { + collection: 'users', + resource: 'users', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + disableTemplate: false, + }, + 'x-designer': 'TableBlockDesigner', + 'x-component': 'CardItem', + 'x-filter-targets': [], + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'TableActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + properties: { + lmeom75gry5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-action': 'create', + 'x-acl-action': 'create', + title: "{{t('Add new')}}", + 'x-designer': 'Action.Designer', + 'x-component': 'Action', + 'x-decorator': 'ACLActionProvider', + 'x-component-props': { + openMode: 'drawer', + type: 'primary', + component: 'CreateRecordAction', + icon: 'PlusOutlined', + }, + 'x-align': 'right', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Add record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializersForCreateFormBlock', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Add new")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'CreateFormBlockInitializers', + 'x-uid': 'w224zhqyair', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'lll44vre1t6', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'c025dgp5tyk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'og2z02rfxhx', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vn9wxzx83y3', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7s3fcxfjc0y', + 'x-async': false, + 'x-index': 1, + }, + zqdtqsjqgc1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'TableColumnInitializers', + 'x-component': 'TableV2', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + useProps: '{{ useTableBlockProps }}', + }, + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-designer': 'TableV2.ActionColumnDesigner', + 'x-initializer': 'TableActionColumnInitializers', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + properties: { + kdcs236lihl: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Edit") }}', + 'x-action': 'update', + 'x-designer': 'Action.Designer', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'drawer', + icon: 'EditOutlined', + }, + 'x-decorator': 'ACLActionProvider', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Edit record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'TabPaneInitializers', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Edit")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'RecordBlockInitializers', + 'x-uid': 's49vs6v3qs0', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '33qff1grgqn', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3z1hbrs3bre', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '09vwzm2det2', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bgdfnken9ua', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'tn98i5lgydw', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ubmt489cxzn', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 't6eg1ye4wf4', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'h4yvac2sy2g', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3xei2593vgu', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'm67du7wrojo', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7aige8a5w3q', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'qpbgsjdsgaq', + 'x-async': true, + 'x-index': 1, +}; + +test.describe('add block & delete block', () => { + test('add block,then delete block', async ({ page, mockPage }) => { + await mockPage().goto(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').click(); + await page.getByLabel('dataBlocks-table', { exact: true }).click(); + await page.getByLabel('dataBlocks-table-Users').click(); + await expect(page.getByLabel('block-item-CardItem-users-table')).toBeVisible(); + await expect(page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users')).toBeVisible(); + await expect(await page.getByLabel('schema-initializer-TableV2-TableColumnInitializers-users')).toBeVisible(); + //删除区块 + await page.getByLabel('block-item-CardItem-users-table').hover(); + await page + .getByLabel('block-item-CardItem-users-table') + .getByRole('button', { name: 'designer-schema-settings' }) + .click(); + await page.getByLabel('Delete').click(); + await page.getByRole('button', { name: 'OK' }).click(); + await expect(page.getByLabel('block-item-CardItem-users-table')).not.toBeVisible(); + await expect(page.getByLabel('schema-initializer-ActionBar-TableActionInitializers-users')).not.toBeVisible(); + await expect(await page.getByLabel('schema-initializer-TableV2-TableColumnInitializers-users')).not.toBeVisible(); + }); +}); + +test.describe('block title', () => { + test('edit block title', async ({ page, mockPage }) => { + await mockPage({ + pageSchema: tablePageSchema, + }).goto(); + await page.getByLabel('block-item-CardItem-users-table').click(); + await page + .getByLabel('block-item-CardItem-users-table') + .getByRole('button', { name: 'designer-schema-settings' }) + .hover(); + await page.getByLabel('Edit block title').click(); + await page.getByLabel('block-item-Input-users-Block title').click(); + await page.getByLabel('block-item-Input-users-Block title').getByRole('textbox').fill('block title'); + await page.locator('.ant-modal').getByRole('button', { name: 'OK' }).click(); + await expect(page.getByLabel('block-item-CardItem-users-table').locator('.ant-card-head')).toBeVisible(); + await expect(await page.getByLabel('block-item-CardItem-users-table').locator('.ant-card-head').innerText()).toBe( + 'block title', + ); + + //回显 + await page + .getByLabel('block-item-CardItem-users-table') + .getByRole('button', { name: 'designer-schema-settings' }) + .click(); + await page.getByText('Edit block title').click(); + const inputValue = await page.getByRole('textbox').inputValue(); + await expect(inputValue).toBe('block title'); + }); +}); + +test.describe('blcok template', () => { + test('save block template', async ({ page, mockPage }) => { + await mockPage({ + name: 'block template source', + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + bg76x03o9f2: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + gdj0ceke8ac: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + ftx8xnesvev: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + tu0dxua38tw: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + resource: 'users', + collection: 'users', + }, + 'x-designer': 'FormV2.Designer', + 'x-component': 'CardItem', + 'x-component-props': {}, + properties: { + avv3vpk0nlv: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-component-props': { + useProps: '{{ useFormBlockProps }}', + }, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'FormItemInitializers', + properties: { + gnw25oyqe56: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + rdbe3gg1qv5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + nickname: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-designer': 'FormItem.Designer', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.nickname', + 'x-component-props': {}, + 'x-uid': 'okrljzl6j7s', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '1zjdduck27k', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'l0cyy3gzz86', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4wrgwkyyf81', + 'x-async': false, + 'x-index': 1, + }, + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'FormActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + marginTop: 24, + }, + }, + 'x-uid': '2apymtcq35d', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '1tnmbrvb9ad', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vo1pyqmoe28', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'z59pkpc8uhq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vsfafj9qcx9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'sdj6iw5b0gs', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'uz4dyz41vt1', + 'x-async': true, + 'x-index': 1, + }, + }).goto(); + await page.getByLabel('block-item-CardItem-users-form').click(); + await page + .getByLabel('block-item-CardItem-users-form') + .getByLabel('designer-schema-settings-CardItem-FormV2.Designer-users') + .click(); + await page.getByLabel('Save as block template').click(); + await page.getByLabel('Save as template').getByRole('textbox').fill('Users_Form'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByLabel('block-item-CardItem-users-form').hover(); + await page.waitForTimeout(1000); + //保存模板后当前区块为引用区块 + const titleTag = await page.getByLabel('block-item-CardItem-users-form').locator('.title-tag').nth(1).innerText(); + await expect(titleTag).toContain('Reference template'); + }); + test('using block template ', async ({ page, mockPage }) => { + await mockPage({ + name: 'block template', + pageSchema: tablePageSchema, + }).goto(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').click(); + //使用复制模板 + await page.getByLabel('dataBlocks-form', { exact: true }).click(); + await page.getByLabel('dataBlocks-form-FormItem_table_subMenu').click(); + await page.getByRole('menuitem', { name: 'Duplicate template right' }).click(); + await page.getByText('Users_Form (Fields only)').first().click(); + await expect(page.getByLabel('block-item-CardItem-users-form')).toBeVisible(); + + //在新建操作中使用引用模板 + await page.getByLabel('action-Action-Add new-create-users-table').click(); + await page.getByLabel('schema-initializer-Grid-CreateFormBlockInitializers-users').click(); + await page.getByRole('menuitem', { name: 'form Form right' }).click(); + await page.getByRole('menuitem', { name: 'Reference template right' }).click(); + await page.getByRole('button', { name: 'Users_Form (Fields only)' }).first().click(); + await page.getByLabel('schema-initializer-Grid-CreateFormBlockInitializers-users').hover(); + await expect(page.locator('.ant-drawer').getByLabel('block-item-CardItem-users-form')).toBeVisible(); + await page.locator('.ant-drawer-mask').click(); + + //在编辑操作中使用引用模板 + await page.getByLabel('block-item-CardItem-users-table').getByLabel('Edit').click(); + await page.getByLabel('schema-initializer-Grid-RecordBlockInitializers-users').click(); + await page.getByRole('menuitem', { name: 'form Form right' }).click(); + await page.getByRole('menuitem', { name: 'Reference template right' }).click(); + await page.getByRole('button', { name: 'Users_Form (Fields only)' }).first().click(); + await page.getByLabel('schema-initializer-Grid-RecordBlockInitializers-users').hover(); + + //修改引用模板 + await page.locator('.ant-drawer').getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Phone').click(); + await page.locator('.ant-drawer-mask').click(); + //复制模板不同步,引用模板同步 + await expect( + page.getByLabel('block-item-CardItem-users-form').getByLabel('block-item-CollectionField-users-form-users.phone'), + ).not.toBeVisible(); + await page.getByLabel('block-item-CardItem-users-table').getByLabel('action-Action-Add').click(); + await expect(await page.getByLabel('block-item-CollectionField-users-form-users.phone')).toBeVisible(); + await page.locator('.ant-drawer-mask').click(); + + //删除模板 + await page.getByTestId('settings-center-button').click(); + await page.getByRole('button', { name: 'All plugin settings' }).click(); + await page.getByRole('menuitem', { name: 'layout Block templates' }).click(); + await page.getByLabel('action-Action.Link-Delete-destroy-uiSchemaTemplates-table-Users_Form').click(); + await page.getByRole('button', { name: 'OK' }).click(); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/field.test.ts b/packages/core/client/src/__tests__/e2e/field.test.ts new file mode 100644 index 000000000..4dfe49fab --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/field.test.ts @@ -0,0 +1,349 @@ +import { expect, test } from '@nocobase/test/client'; +import { approximateColor } from './utils'; +const formPageSchema = { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + lyie34rtcu4: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + skmxkfr67em: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + '860xnb3egb8': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + aa9rqm7bkud: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + resource: 'users', + collection: 'users', + }, + 'x-designer': 'FormV2.Designer', + 'x-component': 'CardItem', + 'x-component-props': {}, + properties: { + t4mi2jywqj7: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-component-props': { + useProps: '{{ useFormBlockProps }}', + }, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'FormItemInitializers', + 'x-uid': '66dozhzo5ld', + 'x-async': false, + 'x-index': 1, + }, + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'FormActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + marginTop: 24, + }, + }, + 'x-uid': 'q8am9zt6m2x', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '4ojxya0re8h', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '183n4o77awp', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'zwxgkj66isi', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8tryd9kz9bd', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'piwq6rrjrsq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5cszm46yqxb', + 'x-async': true, + 'x-index': 1, +}; + +test.describe('add field & remove field in block', () => { + test('add field,then remove field in block', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Nickname').click(); + await page.getByLabel('Display collection fields-Username').click(); + await page.getByLabel('Display collection fields-Email').click(); + //添加字段 + await expect(page.getByLabel('block-item-CollectionField-users-form-users.nickname')).toBeVisible(); + await expect(page.getByLabel('block-item-CollectionField-users-form-users.username')).toBeVisible(); + await expect(page.getByLabel('block-item-CollectionField-users-form-users.email')).toBeVisible(); + //激活的状态 + await expect(await page.getByLabel('Display collection fields-Nickname').getByRole('switch').isChecked()).toBe( + true, + ); + await expect(await page.getByLabel('Display collection fields-Username').getByRole('switch').isChecked()).toBe( + true, + ); + await expect(await page.getByLabel('Display collection fields-Email').getByRole('switch').isChecked()).toBe(true); + //移除字段 + await page.getByLabel('Display collection fields-Nickname').click(); + await page.getByLabel('Display collection fields-Username').click(); + await page.getByLabel('Display collection fields-Email').click(); + await expect(page.getByLabel('block-item-CollectionField-users-form-users.nickname')).not.toBeVisible(); + await expect(page.getByLabel('block-item-CollectionField-users-form-users.username')).not.toBeVisible(); + await expect(page.getByLabel('block-item-CollectionField-users-form-users.email')).not.toBeVisible(); + }); +}); + +test.describe('drag field in block', () => { + test('drag field for layout', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Nickname').click(); + await page.getByLabel('Display collection fields-Username').click(); + await page.getByLabel('Display collection fields-Email').click(); + + const sourceElement = await page.getByLabel('block-item-CollectionField-users-form-users.nickname'); + await sourceElement.hover(); + const source = await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .getByLabel('designer-drag'); + await source.hover(); + + const targetElement = await page.getByLabel('block-item-CollectionField-users-form-users.username'); + await source.dragTo(targetElement); + const targetElement2 = await page.getByLabel('block-item-CollectionField-users-form-users.email'); + await page.getByLabel('block-item-CollectionField-users-form-users.nickname').getByLabel('designer-drag').hover(); + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .getByLabel('designer-drag') + .dragTo(targetElement2); + + const nickname = await source.boundingBox(); + const username = await targetElement.boundingBox(); + const email = await targetElement2.boundingBox(); + const max = Math.max(username.y, nickname.y, email.y); + //拖拽调整排序符合预期 + await expect(nickname.y).toBe(max); + }); +}); + +test.describe('field setting config ', () => { + test('edit field label in block', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Username').click(); + await page.getByLabel('block-item-CollectionField-users-form-users.username').hover(); + await page.getByLabel('designer-schema-settings-CollectionField-FormItem.Designer-users-users.username').click(); + // await page.getByLabel('block-item-CollectionField-users-form-users.username').getByLabel('designer-schema-settings').click(); + await page.getByLabel('Edit field title').click(); + await page.getByLabel('block-item-Input-users-Field title').getByRole('textbox').fill('Username1'); + await page.getByRole('button', { name: 'OK' }).click(); + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.username').getByText('Username1'), + ).toBeVisible(); + //回显 + await page.getByLabel('block-item-CollectionField-users-form-users.username').click(); + await page + .getByLabel('block-item-CollectionField-users-form-users.username') + .getByLabel('designer-schema-settings') + .hover(); + await page.getByLabel('Edit field title').click(); + await page.waitForTimeout(1000); // 等待1秒钟 + await expect(await page.getByLabel('block-item-Input-users-Field title').getByRole('textbox').inputValue()).toBe( + 'Username1', + ); + }); + test('display & not display field label in block ', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Username').click(); + await page.getByLabel('block-item-CollectionField-users-form-users.username').click(); + await page + .getByLabel('block-item-CollectionField-users-form-users.username') + .getByLabel('designer-schema-settings') + .hover(); + await page.getByLabel('Display title').hover(); + //默认显示 + await expect(await page.getByLabel('Display title').getByRole('switch').isChecked()).toBe(true); + //设置不显示标题 + await page.getByLabel('Display title').click(); + const labelItem = page + .getByLabel('block-item-CollectionField-users-form-users.username') + .locator('.ant-formily-item-label'); + const labelDisplay = await labelItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.display; + }); + expect(labelDisplay).toBe('none'); + //设置显示标题 + await page.getByLabel('block-item-CollectionField-users-form-users.username').click(); + await page + .getByLabel('block-item-CollectionField-users-form-users.username') + .getByLabel('designer-schema-settings') + .hover(); + await page.getByLabel('Display title').click(); + const labelDisplay1 = await labelItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.display; + }); + expect(labelDisplay1).toBe('flex'); + }); + test('edit field description ', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + const description = 'field description'; + const descriptionColor = 'rgba(0, 0, 0, 0.65)'; + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Username').click(); + await page.getByLabel('block-item-CollectionField-users-form-users.username').click(); + await page + .getByLabel('block-item-CollectionField-users-form-users.username') + .getByLabel('designer-schema-settings') + .hover(); + await page.getByLabel('Edit description').click(); + await page.getByLabel('block-item-Input.TextArea-users').locator('textarea').fill(description); + await page.getByRole('button', { name: 'OK' }).click(); + const descriptionItem = await page + .getByLabel('block-item-CollectionField-users-form-users.username') + .locator('.ant-formily-item-extra'); + const descriptionItemColor = await descriptionItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.color; + }); + //字段描述样式符合预期 + expect(await descriptionItem.innerText()).toBe(description); + const isApproximate = approximateColor(descriptionItemColor, descriptionColor); + await expect(isApproximate).toBe(true); + }); + test('field required ', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Nickname').click(); + await page.getByLabel('block-item-CollectionField-users-form-users.nickname').click(); + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .getByLabel('designer-schema-settings') + .click(); + await page.getByLabel('Required').click(); + await page.getByLabel('schema-initializer-ActionBar-FormActionInitializers-users').click(); + await page.getByLabel('Enable actions-Submit').click(); + await page.getByLabel('action-Action-Submit-submit-users-form').click(); + await page.waitForTimeout(1000); + //必填校验符合预期 + await expect( + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .locator('.ant-formily-item-error-help'), + ).toBeVisible(); + const inputItem = page.getByLabel('block-item-CollectionField-users-form-users.nickname').locator('input'); + const inputErrorBorderColor = await inputItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.borderColor; + }); + //样式符合预期 + expect(inputErrorBorderColor).toBe('rgb(255, 77, 79)'); + // 断言表单未被提交 + expect(await page.getByLabel('block-item-CardItem-users-form').locator('form')).not.toHaveProperty( + 'submitted', + true, + ); + }); + test('field validation rule ', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + const errorMessage = 'this is error message'; + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Nickname').click(); + + await page.getByLabel('block-item-CollectionField-users-form-users.nickname').click(); + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .getByLabel('designer-schema-settings') + .click(); + await page.getByText('Set validation rules').click(); + await page.getByRole('button', { name: 'plus Add validation rule' }).click(); + await await page.getByLabel('block-item-InputNumber-users-Max length').getByRole('spinbutton').fill('3'); + await page.getByRole('button', { name: 'Error message' }).locator('textarea').fill(errorMessage); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + await page.getByLabel('block-item-CollectionField-users-form-users.nickname').getByRole('textbox').fill('1111'); + const errorInfo = await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .locator('.ant-formily-item-error-help'); + await expect(errorInfo).toBeVisible(); + //报错信息符合预期 + await expect(await errorInfo.innerText()).toBe(errorMessage); + await page.getByLabel('schema-initializer-ActionBar-FormActionInitializers-users').click(); + await page.getByLabel('Enable actions-Submit').click(); + await page.getByLabel('action-Action-Submit-submit-users-form').click(); + await page.waitForTimeout(1000); + // 断言表单未被提交 + expect(await page.getByLabel('block-item-CardItem-users-form').locator('form')).not.toHaveProperty( + 'submitted', + true, + ); + }); + test('field pattern ', async ({ page, mockPage }) => { + await mockPage({ pageSchema: formPageSchema }).goto(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').click(); + await page.getByLabel('Display collection fields-Nickname').click(); + + await page.getByLabel('block-item-CollectionField-users-form-users.nickname').click(); + const inputElement = await page.getByLabel('block-item-CollectionField-users-form-users.nickname').locator('input'); + await page + .getByLabel('block-item-CollectionField-users-form-users.nickname') + .getByLabel('designer-schema-settings') + .click(); + await page.getByLabel('Pattern').click(); + + //禁用 + await page.getByRole('option', { name: 'Readonly' }).click(); + await expect(await inputElement.isDisabled()).toBe(true); + await page.getByLabel('Pattern').click(); + //只读 + await page.getByText('Easy-reading').click(); + await expect( + await page.getByLabel('block-item-CollectionField-users-form-users.nickname').locator('.ant-description-input'), + ).toBeInViewport(); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/menu.test.ts b/packages/core/client/src/__tests__/e2e/menu.test.ts index fc54ffa33..c0f3d12d1 100644 --- a/packages/core/client/src/__tests__/e2e/menu.test.ts +++ b/packages/core/client/src/__tests__/e2e/menu.test.ts @@ -1,22 +1,217 @@ import { enableToConfig, expect, test } from '@nocobase/test/client'; +import { approximateColor } from './utils'; -test.describe('menu', () => { - test('create new page, then delete', async ({ page }) => { - await page.goto('/'); - await enableToConfig(page); - - await page.getByLabel('schema-initializer-Menu-header').hover(); - await page.getByLabel('Page').click(); +test.describe('menu page', () => { + test('create new page, then delete', async ({ page, mockPage }) => { + await mockPage().goto(); + const pageTitle = 'new page'; + await page.getByLabel('schema-initializer-Menu-header').click(); + await page.waitForTimeout(1000); // 等待1秒钟 + await page.getByRole('menu').getByLabel('Page').click(); await page.getByRole('textbox').click(); - await page.getByRole('textbox').fill('new page'); + await page.getByRole('textbox').fill(pageTitle); await page.getByRole('button', { name: 'OK' }).click(); - await page.getByText('new page').click(); - + const menuItem = page.getByRole('menu').locator('li').filter({ hasText: pageTitle }); + const defaultBackgroundColor = await menuItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.backgroundColor; + }); + await menuItem.click(); + // 获取激活后的背景高亮颜色 + const activedBackgroundColor = await menuItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.backgroundColor; + }); + // 菜单高亮/进入空页面只有一个add block 按钮 + expect(activedBackgroundColor).not.toBe(defaultBackgroundColor); await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); + await expect(page.getByTitle(pageTitle)).toBeVisible(); + const divElement = await page.locator('.nb-page-content'); + const buttons = await divElement.locator('button').count(); + const divText = await divElement.textContent(); + + await expect(buttons).toEqual(1); + await expect(divText).toBe('Add block'); // 删除页面,避免影响其他测试 - await page.getByRole('menu').getByText('new page').hover(); - await page.getByLabel('designer-schema-settings-Menu.Item').hover(); + await page.getByLabel(pageTitle).click(); + await page.getByLabel(pageTitle).getByLabel('designer-schema-settings').hover(); + await page.getByLabel('Delete').click(); + await page.getByRole('button', { name: 'OK' }).click(); + await mockPage().goto(); + await expect(page.getByTitle(pageTitle)).not.toBeVisible(); + }); + test('edit menu title', async ({ page, mockPage }) => { + const pageTitle = 'page title'; + const newPagetitle = 'page title1'; + await mockPage({ + name: pageTitle, + }).goto(); + await page.getByLabel(pageTitle).hover(); + await page.getByLabel(pageTitle).getByLabel('designer-schema-settings').hover(); + await page.getByLabel('Edit').click(); + await page.getByRole('textbox').click(); + await page.getByRole('textbox').fill(newPagetitle); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByRole('menu').getByText(newPagetitle).click(); + await mockPage().goto(); + await page.getByText(newPagetitle).click(); + await expect(page.getByTitle(newPagetitle)).toBeVisible(); + }); + test('move menu ', async ({ page, mockPage }) => { + const pageTitle1 = 'page1'; + const pageTitle2 = 'page2'; + await mockPage({ + name: pageTitle1, + }).goto(); + await mockPage({ name: pageTitle2 }).goto(); + await enableToConfig(page); + await page.getByRole('menu').getByText(pageTitle1).click(); + await page.getByRole('menu').getByText(pageTitle1).hover(); + await page.getByLabel(pageTitle1).getByLabel('designer-schema-settings').hover(); + await page.getByLabel('Move to').click(); + await page.getByRole('dialog').click(); + await page.getByLabel('Search').click(); + await page.waitForTimeout(1000); // 等待1秒钟 + await page.locator('.ant-select-dropdown').getByText(pageTitle2).click(); + await page.getByRole('button', { name: 'OK' }).click(); + const page1 = await page.getByRole('menu').getByText(pageTitle1).boundingBox(); + const page2 = await page.getByRole('menu').getByText(pageTitle2).boundingBox(); + //拖拽菜单排序符合预期 + expect(page2.x).toBeLessThan(page1.x); + }); + + test('insert page before', async ({ page, mockPage }) => { + const pageTitle3 = 'page3'; + const pageTitle4 = 'page4'; + await mockPage({ + name: pageTitle3, + }).goto(); + + await page.getByLabel(pageTitle3).click(); + await page.getByLabel(pageTitle3).hover(); + await page.getByLabel(pageTitle3).getByLabel('designer-schema-settings').hover(); + + await page.getByLabel('Insert before').hover(); + + await page.getByRole('button', { name: 'Add page' }).click(); + await page.getByRole('textbox').fill(pageTitle4); + await page.getByRole('button', { name: 'OK' }).click(); + + const page3 = await page.getByLabel(pageTitle3).boundingBox(); + const page4 = await page.getByLabel(pageTitle4).boundingBox(); + //插入的菜单位置符合预期,且进入空页面 + expect(page4.x).toBeLessThan(page3.x); + await page.getByRole('menu').getByText(pageTitle4).click(); + await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); + //删除页面 + await page.getByLabel(pageTitle4).click(); + await page.getByLabel(pageTitle4).getByLabel('designer-schema-settings').hover(); + await page.getByLabel('Delete').click(); + await page.getByRole('button', { name: 'OK' }).click(); + }); + + test('insert page after', async ({ page, mockPage }) => { + const pageTitle5 = 'page5'; + const pageTitle6 = 'page6'; + await mockPage({ + name: pageTitle5, + }).goto(); + + await page.getByLabel(pageTitle5).click(); + await page.getByLabel(pageTitle5).hover(); + await page.getByLabel(pageTitle5).getByLabel('designer-schema-settings').hover(); + + await page.getByLabel('Insert after').hover(); + + await page.getByRole('button', { name: 'Add page' }).click(); + await page.getByRole('textbox').fill(pageTitle6); + await page.getByRole('button', { name: 'OK' }).click(); + + const page6 = await page.getByLabel(pageTitle6).boundingBox(); + const page5 = await page.getByLabel(pageTitle5).boundingBox(); + //插入的菜单位置符合预期 + expect(page5.x).toBeLessThan(page6.x); + await page.getByLabel(pageTitle6).click(); + await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); + //删除页面 + await page.getByLabel(pageTitle6).getByLabel('designer-schema-settings-Menu.Item').click(); + await page.getByRole('menu').getByLabel('Delete').click(); + await page.getByRole('button', { name: 'OK' }).click(); + }); +}); + +test.describe('menu group', () => { + test('create new menu group, then delete', async ({ page, mockPage }) => { + await mockPage().goto(); + await page.getByLabel('schema-initializer-Menu-header').hover(); + await page.getByRole('menu').getByLabel('Group').click(); + await page.getByRole('textbox').click(); + await page.getByRole('textbox').fill('menu Group'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByText('menu Group').click(); + + await expect(page.getByLabel('schema-initializer-Menu-side')).toBeVisible(); + const sideBar = await page.locator('ul').filter({ hasText: /^Add menu item$/ }); + await expect(sideBar).toBeVisible(); + + //添加子页面 + await page + .locator('ul') + .filter({ hasText: /^Add menu item$/ }) + .click(); + await page.getByLabel('schema-initializer-Menu-side').click(); + await page.getByRole('button', { name: 'Page' }).click(); + await page.getByRole('textbox').click(); + await page.getByRole('textbox').fill('group page'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByText('group page').click(); + //进入子页面 + await expect(page.getByTitle('group page')).toBeVisible(); + //对应分组/子菜单项均高亮 + const menuItem = await page.getByRole('menu').locator('li').filter({ hasText: 'menu Group' }); + const menuItemBackgroundColor = await menuItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.backgroundColor; + }); + await page.waitForTimeout(1000); // 等待1秒钟 + const isApproximate = approximateColor(menuItemBackgroundColor, 'rgba(255, 255, 255, 0.1)'); + await expect(isApproximate).toBe(true); + const pageItem = await page.getByRole('menu').locator('li').filter({ hasText: 'group page' }); + + const pageItemBackgroundColor = await pageItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.backgroundColor; + }); + await expect(pageItemBackgroundColor).toBe('rgb(230, 244, 255)'); + + // 删除页面,避免影响其他测试 + await page.getByLabel('menu Group').click(); + await page.getByLabel('menu Group').hover(); + await page.getByLabel('menu Group').getByLabel('designer-schema-settings').first().hover(); + await page.getByLabel('Delete').click(); + await page.getByRole('button', { name: 'OK' }).click(); + await mockPage().goto(); + await expect(page.getByTitle('menu Group')).not.toBeVisible(); + }); +}); + +test.describe('menu link', () => { + test('create new menu link, then delete', async ({ page, mockPage }) => { + await mockPage().goto(); + await page.getByLabel('schema-initializer-Menu-header').hover(); + await page.getByRole('menu').getByLabel('Link').click(); + await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').fill('link menu'); + await page.getByLabel('block-item-Input-Link').getByRole('textbox').fill('https://www.baidu.com/'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByLabel('link menu').click(); + const page2Promise = page.waitForEvent('popup'); + const page2 = await page2Promise; + + await expect(page2.getByRole('button', { name: '百度一下' })).toBeVisible(); + // 删除页面,避免影响其他测试 + await page.getByLabel('link menu').click(); + await page.getByLabel('link menu').getByLabel('designer-schema-settings').hover(); await page.getByLabel('Delete').click(); await page.getByRole('button', { name: 'OK' }).click(); }); diff --git a/packages/core/client/src/__tests__/e2e/page.test.ts b/packages/core/client/src/__tests__/e2e/page.test.ts new file mode 100644 index 000000000..954d8b5c7 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/page.test.ts @@ -0,0 +1,189 @@ +import { enableToConfig, expect, test } from '@nocobase/test/client'; + +test.describe('page header', () => { + test('disabled & enabled page header', async ({ page, mockPage }) => { + const pageTitle = 'page header'; + await mockPage({ name: pageTitle }).goto(); + //默认开启 + await expect(page.getByTitle(pageTitle)).toBeVisible(); + await page.getByTitle(pageTitle).click(); + await page.getByLabel('designer-schema-settings-Page').hover(); + await expect(page.getByLabel('Enable page header').getByRole('switch')).toBeChecked(); + //关闭 + await page.getByLabel('Enable page header').getByRole('switch').click(); + await expect(await page.locator('.ant-page-header')).not.toBeVisible(); + await expect(page.getByLabel('Enable page header').getByRole('switch')).not.toBeChecked(); + //开启 + await page.getByRole('main').locator('span').nth(1).click(); + await page.getByLabel('designer-schema-settings-Page').hover(); + await page.getByLabel('Enable page header').getByRole('switch').click(); + await expect(await page.locator('.ant-page-header').getByTitle(pageTitle)).toBeVisible(); + }); +}); + +test.describe('page title', () => { + test('disable & not disable page title', async ({ page, mockPage }) => { + const pageTitle = 'page title'; + await mockPage({ name: pageTitle }).goto(); + //默认显示 + await expect(page.getByTitle(pageTitle)).toBeVisible(); + await page.getByTitle(pageTitle).click(); + await page.getByLabel('designer-schema-settings-Page').hover(); + await expect(page.getByLabel('Display page title').getByRole('switch')).toBeChecked(); + //不显示 + await page.getByLabel('Display page title').getByRole('switch').click(); + await expect(page.locator('.ant-page-header')).toBeVisible(); + await expect(page.locator('.ant-page-header').getByTitle(pageTitle)).not.toBeVisible(); + await expect(page.getByLabel('Display page title').getByRole('switch')).not.toBeChecked(); + //开启 + await page.locator('.ant-page-header').click(); + await page.getByLabel('designer-schema-settings-Page').hover(); + await page.getByLabel('Display page title').getByRole('switch').click(); + await expect(page.locator('.ant-page-header').getByTitle(pageTitle)).toBeVisible(); + }); + test('edit page title', async ({ page, mockPage }) => { + await mockPage({ name: 'page title1' }).goto(); + + await expect(page.getByTitle('page title1')).toBeVisible(); + await page.getByTitle('page title1').click(); + await page.getByLabel('designer-schema-settings-Page').hover(); + await page.getByText('Edit page title').click(); + await page.getByRole('textbox').click(); + await page.getByRole('textbox').fill('page title2'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByText('page title2').click(); + await expect(page.getByText('page title2')).toBeVisible(); + //菜单栏不调整 + await expect(page.getByLabel('page title1')).toBeVisible(); + }); +}); + +test.describe('page tabs', () => { + test('enable & disabled page tab', async ({ page, mockPage }) => { + await mockPage({ name: 'page tab' }).goto(); + await page.getByTitle('page tab').click(); + await page.getByLabel('designer-schema-settings-Page').hover(); + //默认不启用 + await expect(page.getByLabel('Enable page tabs').getByRole('switch')).not.toBeChecked(); + //启用标签 + await page.getByLabel('Enable page tabs').click(); + await expect(page.getByRole('tab').locator('div').filter({ hasText: 'Unnamed' })).toBeVisible(); + await expect(page.getByLabel('schema-initializer-Page-tabs')).toBeVisible(); + await page.getByRole('tab').locator('div').filter({ hasText: 'Unnamed' }).click(); + await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); + + //添加新的tab + await page.getByLabel('schema-initializer-Page-tabs').click(); + await page.getByRole('textbox').click(); + await page.getByRole('textbox').fill('page tab 1'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByText('page tab 1').click(); + await page.getByLabel('schema-initializer-Page-tabs').click(); + await page.getByRole('textbox').click(); + await page.getByRole('textbox').fill('page tab 2'); + await page.getByRole('button', { name: 'OK' }).click(); + await page.getByText('page tab 2').click(); + + await page.waitForTimeout(1000); // 等待1秒钟 + const tabMenuItem = await page.getByRole('tab').locator('div > span').filter({ hasText: 'page tab 2' }); + const tabMenuItemActivedColor = await tabMenuItem.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.color; + }); + //激活的tab样式符合预期 + await expect(page.getByText('page tab 1')).toBeVisible(); + await expect(page.getByText('page tab 2')).toBeVisible(); + await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); + await expect(tabMenuItemActivedColor).toBe('rgb(22, 119, 255)'); + + //修改tab名称 + await page.getByText('Unnamed').click(); + await page.getByRole('button', { name: 'designer-schema-settings-Page-tab' }).click(); + await page.getByLabel('Edit tab').click(); + await page.getByRole('textbox').fill('page tab'); + await page.getByRole('button', { name: 'OK' }).click(); + + const tabMenuItem1 = await page.getByRole('tab').getByText('page tab', { exact: true }); + const tabMenuItemActivedColor1 = await tabMenuItem1.evaluate((element) => { + const computedStyle = window.getComputedStyle(element); + return computedStyle.color; + }); + await expect(tabMenuItem1).toBeVisible(); + await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); + await expect(tabMenuItemActivedColor1).toBe('rgb(22, 119, 255)'); + + //删除 tab + await page.getByRole('tab').getByText('page tab', { exact: true }).hover(); + await page.getByRole('button', { name: 'designer-schema-settings-Page-tab' }).click(); + await page.getByRole('button', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + await expect(page.getByRole('tab').getByText('page tab', { exact: true })).not.toBeVisible(); + await page.getByRole('tab').getByText('page tab 1').click(); + + //禁用标签 + await page + .locator('div') + .filter({ hasText: /^page tab$/ }) + .nth(1) + .click(); + await page.getByLabel('designer-schema-settings-Page', { exact: true }).click(); + await page.getByLabel('Enable page tabs').getByRole('switch').setChecked(false); + await expect(page.getByText('page tab 2')).not.toBeVisible(); + }); + test('drag page tab sorting', async ({ page, mockPage }) => { + await mockPage({ + pageSchema: { + 'x-uid': 'h8q2mcgo3cq', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-component-props': { + enablePageTabs: true, + }, + properties: { + bi8ep3svjee: { + 'x-uid': '9kr7xm9x4ln', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + title: 'tab 1', + 'x-async': false, + 'x-index': 1, + }, + rw91udnzpr3: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'tab 2', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + 'x-uid': 'o5vp90rqsjx', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-async': true, + 'x-index': 1, + }, + }).goto(); + + const sourceElement = await page.locator('span:has-text("tab 2")'); + await sourceElement.hover(); + const source = await page.getByRole('button', { name: 'drag' }); + await source.hover(); + const targetElement = await page.locator('span:has-text("tab 1")'); + const sourceBoundingBox = await sourceElement.boundingBox(); + const targetBoundingBox = await targetElement.boundingBox(); + //拖拽标签调整排序 拖拽前 1-2 + expect(targetBoundingBox.x).toBeLessThan(sourceBoundingBox.x); + await source.dragTo(targetElement); + await sourceElement.dragTo(targetElement); + const tab2 = await page.locator('span:has-text("tab 2")').boundingBox(); + const tab1 = await page.locator('span:has-text("tab 1")').boundingBox(); + //拖拽后 2-1 + await expect(tab2.x).toBeLessThan(tab1.x); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/pm.test.ts b/packages/core/client/src/__tests__/e2e/pm.test.ts new file mode 100644 index 000000000..8e82ebedc --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/pm.test.ts @@ -0,0 +1,121 @@ +import { expect, test } from '@nocobase/test/client'; + +async function waitForModalToBeHidden(page) { + await page.waitForFunction(() => { + const modal = document.querySelector('.ant-modal'); + if (modal) { + const computedStyle = window.getComputedStyle(modal); + return computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; + } + return true; // 如果找不到modal,也算作不可见 + }); +} + +test.describe('add plugin in front', () => { + test('add plugin npm registry,then remove plugin', async ({ page, mockPage }) => { + await mockPage().goto(); + await page.getByTestId('plugin-manager-button').click(); + await expect(page.getByLabel('sample-custom-collection-template')).not.toBeVisible(); + await page.getByRole('button', { name: 'Add new' }).click(); + await page + .getByLabel('block-item-Input-Npm package name') + .getByRole('textbox') + .fill('@nocobase/plugin-sample-custom-collection-template'); + await page.getByLabel('Submit').click(); + await page.waitForTimeout(1000); // 等待1秒钟 + //等待页面刷新结束 + await page.waitForFunction(() => { + const modal = document.querySelector('.ant-modal'); + if (modal) { + const computedStyle = window.getComputedStyle(modal); + return computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; + } + return true; // 如果找不到modal,也算作不可见 + }); + await page.waitForLoadState('load'); + await page.getByPlaceholder('Search plugin').fill('sample-custom-collection-template'); + await expect(page.getByLabel('sample-custom-collection-template')).toBeVisible(); + //将添加的插件删除 + await page.getByLabel('sample-custom-collection-template').getByText('Remove').click(); + await page.getByRole('button', { name: 'Yes' }).click(); + await page.waitForTimeout(2000); // 等待2秒钟 + //等待页面刷新结束 + await waitForModalToBeHidden(page); + await page.waitForLoadState('load'); + await page.getByPlaceholder('Search plugin').fill('sample-custom-collection-template'); + await expect(page.getByLabel('sample-custom-collection-template')).not.toBeVisible(); + }); + test.skip('add plugin local upload', async ({ page, mockPage }) => {}); + test.skip('add plugin file url', async ({ page, mockPage }) => {}); +}); + +test.describe('remove plugin', () => { + test('remove plugin,then add plugin', async ({ page, mockPage }) => { + await mockPage().goto(); + await page.getByTestId('plugin-manager-button').click(); + //hello插件默认安装未启用 + await page.getByPlaceholder('Search plugin').fill('Hello'); + await expect(page.getByLabel('Hello')).toBeVisible(); + const isActive = await page.getByLabel('Hello').getByLabel('enable').isChecked(); + await expect(isActive).toBe(false); + //将hello插件remove + await page.getByLabel('Hello').getByText('Remove').click(); + await page.getByRole('button', { name: 'Yes' }).click(); + //等待页面刷新结束 + await page.waitForLoadState('load'); + await page.getByPlaceholder('Search plugin').fill('hello'); + await expect(page.getByLabel('Hello')).not.toBeVisible(); + //将删除的插件加回来 + await page.getByRole('button', { name: 'Add new' }).click(); + await page + .getByLabel('block-item-Input-Npm package name') + .getByRole('textbox') + .fill('@nocobase/plugin-sample-hello'); + await page.getByLabel('Submit').click(); + await page.waitForTimeout(1000); + //等待弹窗消失和页面刷新结束 + await page.waitForFunction(() => { + const modal = document.querySelector('.ant-modal'); + if (modal) { + const computedStyle = window.getComputedStyle(modal); + return computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; + } + return true; // 如果找不到modal,也算作不可见 + }); + await page.waitForLoadState('load'); + await page.getByPlaceholder('Search plugin').fill('hello'); + await expect(page.getByLabel('Hello')).toBeVisible(); + //已启用的插件不能remove,如ACL + await page.getByPlaceholder('Search plugin').fill('ACL'); + await expect(page.getByLabel('ACL')).toBeVisible(); + const isAclActive = await page.getByLabel('ACL').getByLabel('enable').isChecked(); + expect(isAclActive).toBe(true); + await expect(page.getByLabel('ACL').getByText('Remove')).not.toBeVisible(); + }); +}); + +test.describe('enable & disabled plugin', () => { + test('enable plugin', async ({ page, mockPage }) => { + await mockPage().goto(); + await page.getByTestId('plugin-manager-button').click(); + await page.getByPlaceholder('Search plugin').fill('hello'); + await expect(page.getByLabel('Hello')).toBeVisible(); + const isActive = await page.getByLabel('Hello').getByLabel('enable').isChecked(); + expect(isActive).toBe(false); + //激活插件 + await page.getByLabel('Hello').getByLabel('enable').click(); + await page.waitForTimeout(1000); // 等待1秒钟 + //等待弹窗消失和页面刷新结束 + await waitForModalToBeHidden(page); + await page.waitForLoadState('load'); + await page.getByPlaceholder('Search plugin').fill('hello'); + await expect(await page.getByLabel('Hello').getByLabel('enable').isChecked()).toBe(true); + //将激活的插件禁用 + await page.getByLabel('Hello').getByLabel('enable').click(); + await page.waitForTimeout(1000); // 等待1秒钟 + //等待弹窗消失和页面刷新结束 + await waitForModalToBeHidden(page); + await page.waitForLoadState('load'); + await expect(await page.getByLabel('Hello').getByLabel('enable').isChecked()).toBe(false); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/utils.ts b/packages/core/client/src/__tests__/e2e/utils.ts new file mode 100644 index 000000000..9c05efbf3 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/utils.ts @@ -0,0 +1,33 @@ +//颜色值的近似比较,而不是精确比较 +function approximateColor(actualColor, expectedColor) { + const regex = /rgba?\((\d+), (\d+), (\d+)(?:, ([0-9.]+))?\)/; + const actualMatch = actualColor.match(regex); + const expectedMatch = expectedColor.match(regex); + + if (actualMatch && expectedMatch) { + const tolerance = 2; // 允许的颜色分量误差 + for (let i = 1; i <= 4; i++) { + const actualValue = parseInt(actualMatch[i], 10); + const expectedValue = parseInt(expectedMatch[i], 10); + if (Math.abs(actualValue - expectedValue) > tolerance) { + return false; + } + } + return true; + } + + return false; +} + +async function waitForModalToBeHidden(page) { + await page.waitForFunction(() => { + const modal = document.querySelector('.ant-modal'); + if (modal) { + const computedStyle = window.getComputedStyle(modal); + return computedStyle.display === 'none' || computedStyle.visibility === 'hidden'; + } + return true; // 如果找不到modal,也算作不可见 + }); +} + +export { approximateColor, waitForModalToBeHidden }; diff --git a/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx b/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx index 09f8816ee..1a5698719 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx @@ -32,7 +32,6 @@ export const ActionModal: ComposedActionDrawer = observer( return ( - + - + {t(field.title)} diff --git a/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx b/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx index b9310032b..f49a18a36 100644 --- a/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx +++ b/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx @@ -133,7 +133,7 @@ export const PageTabDesigner = ({ schema }) => { return (
- + diff --git a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx index c214275b8..918968233 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx +++ b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx @@ -145,6 +145,7 @@ export const FormButtonLinkageRuleAction = observer(