test: client ui test (#2736)

* test: add menu group test

* test: add menu link  test

* test: insert befort page test

* test(e2e): better locators for designer buttons

* fix: make test passing

* test: esit menu title

* test: move menu

* test: move menu

* test: move menu

* test: menu insert page before

* test: menu insert page before

* test: menu test

* test: menu test

* test: menu test

* test: menu test

* test: menu test

* test: menu test

* test: insert page after

* test: insert page after

* test: create new page

* test: menu test refactor

* test: page test

* test: page header

* test: page title

* test: edit page title

* refactor: remove DesignerControl

* test: page test

* test: page test

* test: page tab test

* test: page tab move test

* test: page tab move test

* test: block test

* chore: better locators

* test: add block test

* test: block test

* test: action & field test

* fix: should not disable add-menu-item

* chore: better test id for block

* chore: optimize Action

* chore: remove role in BlockItem

* test: block test

* test: edit block title

* test: edit block title

* test: plugin manager ui test

* test: plugin manager remove ui test

* test: plugin manager remove ui test

* test: plugin manager remove ui test

* test: plugin manager enable ui test

* feat: improve locators

* test: menu test

* test: menu test

* test: menu test

* test: page  test

* test: page test

* test: block test

* test: block test

* test: block test

* test: field test

* test: field test

* test: field test

* test: field test

* test: field test

* test: field test

* chore: menu & add block

* test: action test

* chore: initializer

* chore: testid -> aria label

* chore: tabs

* test: action test

* test: action test

* test: action test

* chore: designers

* refactor: optimize schemaInitializer

* refactor: rename

* chore: add collectionName

* chore: block item

* chore: action

* fix: avoid crashting

* chore(e2e): add __E2E__

* chore: all dialog

* test: action test

* test: action test

* test: field test

* test: field test

* test: field test

* test: field test

* test: page test

* chore: add aria-label for block menu

* Revert "chore: add aria-label for block menu"

This reverts commit 6a840ef816ee1095484dc268b5dfa1bbe6cd8cbe.

* test: action test

* test: page test

* chore: optimize aria-label of Action

* test:  pm test

* chore: schema-initializer

* test:  pm test

* chore(e2e): increase timeout

* chore: schema settings

* chore: optimize table

* chore: workflow

* chore: plugin manager

* chore: collection manager and workflow

* chore: details of workflow

* chore: remove testid of Select

* test: fix unit-tests

* test: fix unit-tests

* test: menu test

* test: page test

* test(e2e): passing tests

* test: page test

* test: block test

* test: field test

* test: fix unit test

* chore: should use hover

* test: field test

* test: passing tests

* chore: passing tests

* test: action test

* chore: fix CI

* chore: fix CI

* test: pm test

* chore: increase timeout in CI

* test: blcok test

* revert: plugin card

* test: pm test

* test: pm test

* chore: remove testid for Modal

* refactor: rename testid

* fix: __E2E__ should be true in CI

* fix(buildPlugin): __E2E__ should be true in CI

* fix: __E2E__

* Revert "fix: __E2E__"

This reverts commit c899a33761fbfab94283f6bf41eae197f889068b.

* fix: __E2E__

* fix: passing tests

* test: action test optimize

* test: menu page pm test

* test: menu page pm test

---------

Co-authored-by: Rain <958414905@qq.com>
Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
katherinehhh 2023-11-04 12:39:44 +08:00 committed by GitHub
parent 9ec2b50990
commit 9a730a8fec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1819 additions and 23 deletions

View File

@ -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;
}

View File

@ -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');
});
});

View File

@ -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();
});
});

View File

@ -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();
});
});

View File

@ -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();
});

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -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 };

View File

@ -32,7 +32,6 @@ export const ActionModal: ComposedActionDrawer<ModalProps> = observer(
return (
<Modal
data-testid="action-modal"
width={actualWidth}
title={field.title}
{...(others as ModalProps)}

View File

@ -555,7 +555,7 @@ Menu.URL = observer(
label: (
<SchemaContext.Provider value={schema}>
<FieldContext.Provider value={field}>
<SortableItem className={designerCss} removeParentsIfNoChildren={false}>
<SortableItem className={designerCss} removeParentsIfNoChildren={false} aria-label={t(field.title)}>
<Icon type={icon} />
<span
style={{
@ -601,7 +601,11 @@ Menu.SubMenu = observer(
label: (
<SchemaContext.Provider value={schema}>
<FieldContext.Provider value={field}>
<SortableItem className={subMenuDesignerCss} removeParentsIfNoChildren={false}>
<SortableItem
className={subMenuDesignerCss}
removeParentsIfNoChildren={false}
aria-label={t(field.title)}
>
<Icon type={icon} />
{t(field.title)}
<Designer />

View File

@ -133,7 +133,7 @@ export const PageTabDesigner = ({ schema }) => {
return (
<div className={'general-schema-designer'}>
<div className={'general-schema-designer-icons'}>
<Space size={2} align={'center'}>
<Space size={3} align={'center'}>
<DragHandler>
<DragOutlined style={{ marginRight: 0 }} role="button" aria-label={getAriaLabel('drag-handler', 'tab')} />
</DragHandler>

View File

@ -145,6 +145,7 @@ export const FormButtonLinkageRuleAction = observer(
<div style={{ marginBottom: 8 }}>
<Space>
<Select
data-testid="select-linkage-properties"
popupMatchSelectWidth={false}
value={operator}
options={operators}

View File

@ -613,12 +613,14 @@ export class Collection<
});
for (const model of models) {
await model.sync(syncOptions || {
await model.sync(
syncOptions || {
force: false,
alter: {
drop: false,
},
});
},
);
}
}