From 29bf187fbf5d2b2dc80eea6ba9460286998dae7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=9B=A8=E6=B0=B4=E8=BF=87=E6=BB=A4=E7=9A=84?= =?UTF-8?q?=E7=A9=BA=E6=B0=94-Rain?= <958414905@qq.com> Date: Fri, 27 Oct 2023 15:32:17 +0800 Subject: [PATCH] chore: optimize locators (#2833) * test(e2e): better locators for designer buttons * fix: make test passing * refactor: remove DesignerControl * chore: better locators * fix: should not disable add-menu-item * chore: better test id for block * chore: optimize Action * chore: remove role in BlockItem * feat: improve locators * chore: menu & add block * chore: initializer * chore: testid -> aria label * chore: tabs * 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 * chore: add aria-label for block menu * Revert "chore: add aria-label for block menu" This reverts commit 6a840ef816ee1095484dc268b5dfa1bbe6cd8cbe. * chore: optimize aria-label of Action * chore: schema-initializer * 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(e2e): passing tests * test: fix unit test * chore: should use hover * test: passing tests * chore: passing tests * chore: fix CI * chore: fix CI * chore: increase timeout in CI --------- Co-authored-by: chenos --- .env.e2e.example | 1 + packages/core/build/src/buildClient.ts | 1 + packages/core/build/src/buildPlugin.ts | 13 +- .../src/__tests__/e2e/bugs/T-2165.test.ts | 8 +- .../src/__tests__/e2e/bugs/T-2174.test.ts | 8 +- .../src/__tests__/e2e/bugs/T-2176.test.ts | 12 +- .../src/__tests__/e2e/bugs/T-2183.test.ts | 10 +- .../src/__tests__/e2e/bugs/T-2186.test.ts | 4 +- .../src/__tests__/e2e/bugs/T-2187.test.ts | 6 +- .../src/__tests__/e2e/bugs/T-2200.test.ts | 36 +-- .../src/__tests__/e2e/checkTableRow.test.ts | 6 +- .../__tests__/e2e/createCollections.test.ts | 8 +- .../src/__tests__/e2e/createDataBlock.test.ts | 40 +-- .../client/src/__tests__/e2e/menu.test.ts | 10 +- .../src/acl/Configuration/StrategyActions.tsx | 2 +- .../src/block-provider/BlockProvider.tsx | 50 ++-- .../block-provider/CalendarBlockProvider.tsx | 2 +- .../block-provider/DetailsBlockProvider.tsx | 2 +- .../FilterFormBlockProvider.tsx | 4 +- .../src/block-provider/FormBlockProvider.tsx | 2 +- .../src/block-provider/FormFieldProvider.tsx | 2 +- .../src/block-provider/GanttBlockProvider.tsx | 2 +- .../block-provider/KanbanBlockProvider.tsx | 2 +- .../src/block-provider/TableBlockProvider.tsx | 2 +- .../src/block-provider/TableFieldProvider.tsx | 2 +- .../block-provider/TableSelectorProvider.tsx | 3 +- .../hooks/useFormActiveFields.tsx | 5 + .../Configuration/AddCollectionAction.tsx | 9 +- .../Configuration/AddFieldAction.tsx | 8 +- .../Configuration/CollectionFields.tsx | 13 +- .../Configuration/ConfigurationTabs.tsx | 7 +- .../Configuration/EditCollectionAction.tsx | 3 +- .../Configuration/EditFieldAction.tsx | 4 +- .../Configuration/components/index.tsx | 8 +- .../Configuration/schemas/collectionFields.ts | 2 + .../Configuration/schemas/collections.ts | 2 + .../templates/components/PreviewFields.tsx | 2 - .../client/src/global-theme/defaultTheme.ts | 1 + packages/core/client/src/pm/PluginCard.tsx | 10 +- packages/core/client/src/pm/PluginManager.tsx | 2 + .../core/client/src/pm/PluginManagerLink.tsx | 4 +- .../antd/action/Action.Drawer.tsx | 6 +- .../antd/action/Action.Modal.tsx | 6 + .../schema-component/antd/action/Action.tsx | 12 +- .../antd/action/demos/demo5.tsx | 2 +- .../action/hooks/useGetAriaLabelOfAction.ts | 40 +++ .../action/hooks/useGetAriaLabelOfDrawer.ts | 28 ++ .../action/hooks/useGetAriaLabelOfModal.ts | 28 ++ .../action/hooks/useGetAriaLabelOfPopover.ts | 28 ++ .../action/hooks/useSetAriaLabelForDrawer.ts | 27 ++ .../action/hooks/useSetAriaLabelForModal.ts | 29 +++ .../action/hooks/useSetAriaLabelForPopover.ts | 20 ++ .../schema-component/antd/action/index.tsx | 4 + .../appends-tree-select/AppendsTreeSelect.tsx | 13 +- .../antd/association-field/InternalPicker.tsx | 1 - .../InternalPopoverNester.tsx | 10 + ...sociationFilter.FilterBlockInitializer.tsx | 1 - .../AssociationFilter.Initializer.tsx | 1 - .../antd/block-item/BlockItem.tsx | 20 +- .../hooks/useGetAriaLabelOfBlockItem.ts | 36 +++ .../antd/calendar/ViewSelect.tsx | 2 +- .../antd/card-item/CardItem.tsx | 13 +- .../collection-select/CollectionSelect.tsx | 4 +- .../antd/color-select/ColorSelect.tsx | 2 +- .../schema-component/antd/cron/CronSet.tsx | 1 - .../antd/filter/DynamicComponent.tsx | 18 +- .../antd/filter/FilterGroup.tsx | 6 +- .../antd/filter/FilterItem.tsx | 10 +- .../antd/form-item/FormItem.tsx | 3 +- .../antd/form-v2/Templates.tsx | 1 - .../antd/grid-card/GridCard.Decorator.tsx | 2 +- .../src/schema-component/antd/input/Input.tsx | 2 + .../antd/kanban/Kanban.Card.Designer.tsx | 15 +- .../antd/list/List.Decorator.tsx | 2 +- .../src/schema-component/antd/menu/Menu.tsx | 25 +- .../antd/menu/MenuItemInitializers/index.tsx | 1 + .../src/schema-component/antd/page/Page.tsx | 5 +- .../antd/page/PageTabDesigner.tsx | 17 +- .../antd/record-picker/InputRecordPicker.tsx | 1 - .../antd/remote-select/RemoteSelect.tsx | 14 +- .../schema-component/antd/select/Select.tsx | 46 +++- .../antd/select/__tests__/select.test.tsx | 3 +- .../table-v2/Table.ActionColumnDesigner.tsx | 4 +- .../antd/table-v2/Table.Column.Decorator.tsx | 5 +- .../antd/table-v2/Table.Column.tsx | 2 +- .../schema-component/antd/table-v2/Table.tsx | 29 ++- .../antd/table/Table.Array.tsx | 19 +- .../antd/table/Table.RowActionDesigner.tsx | 4 +- .../schema-component/antd/variable/Input.tsx | 28 +- .../antd/variable/TextArea.tsx | 2 + .../antd/variable/XButton.tsx | 4 +- .../schema-component/antd/variable/style.ts | 4 +- .../common/sortable-item/SortableItem.tsx | 2 + .../schema-initializer/SchemaInitializer.tsx | 83 ++++-- .../buttons/BlockInitializers.tsx | 1 - .../buttons/BulkEditFormItemInitializers.tsx | 8 +- .../buttons/CalendarActionInitializers.tsx | 1 - .../CalendarFormActionInitializers.tsx | 1 - .../buttons/CustomFormItemInitializers.tsx | 1 - .../buttons/DetailsActionInitializers.tsx | 1 - .../buttons/FilterFormActionInitializers.tsx | 1 - .../buttons/FormActionInitializers.tsx | 4 - .../buttons/FormItemInitializers.tsx | 2 - .../buttons/GridCardActionInitializers.tsx | 2 - .../buttons/KanbanActionInitializers.tsx | 1 - .../buttons/ListActionInitializers.tsx | 2 - .../ReadPrettyFormActionInitializers.tsx | 1 - .../ReadPrettyFormItemInitializers.tsx | 1 - .../buttons/RecordFormBlockInitializers.tsx | 1 - .../buttons/SubTableActionInitializers.tsx | 1 - .../buttons/TabPaneInitializers.tsx | 3 + .../buttons/TableActionColumnInitializers.tsx | 6 +- .../buttons/TableActionInitializers.tsx | 1 - .../buttons/TableColumnInitializers.tsx | 3 - .../buttons/TableSelectorInitializers.tsx | 1 - .../components/BulkEditField.tsx | 2 +- .../components/CreateRecordAction.tsx | 35 +-- .../components/DuplicateAction.tsx | 245 +++++++++--------- .../useGetAriaLabelOfSchemaInitializer.ts | 25 ++ .../items/DuplicateActionInitializer.tsx | 2 +- .../items/MarkdownBlockInitializer.tsx | 5 +- .../client/src/schema-initializer/types.ts | 1 + .../client/src/schema-initializer/utils.ts | 2 +- .../src/schema-items/OpenModeSchemaItems.tsx | 3 +- .../DataTemplates/components/Designer.tsx | 3 +- .../schema-settings/GeneralSchemaDesigner.tsx | 157 ++++------- .../LinkageRules/LinkageRuleAction.tsx | 5 +- .../LinkageRules/ValueDynamicComponent.tsx | 1 - .../src/schema-settings/SchemaSettings.tsx | 137 +++++++--- .../hooks/useGetAriaLabelOfDesigner.ts | 27 ++ .../core/client/src/user/ChangePassword.tsx | 1 + packages/core/client/src/user/CurrentUser.tsx | 12 +- packages/core/client/src/user/EditProfile.tsx | 5 +- .../core/client/src/user/LanguageSettings.tsx | 4 +- packages/core/client/src/user/SwitchRole.tsx | 81 +++--- packages/core/devtools/umiConfig.js | 1 + .../plugin-api-doc/src/client/Document.tsx | 1 - .../client/Configuration/ExpiresSelect.tsx | 7 +- .../src/client/AuditLogsBlockProvider.tsx | 4 +- .../AuditLogsTableActionInitializers.tsx | 1 - .../src/client/ChartBlockEngineDesigner.tsx | 3 +- .../src/client/select/CustomSelect.tsx | 1 - .../client/block/ChartBlockInitializer.tsx | 3 + .../src/client/renderer/ChartRenderer.tsx | 4 +- .../__tests__/e2e/createLocalStorage.test.ts | 16 +- .../__tests__/e2e/editLocalStorage.test.ts | 25 +- .../__tests__/e2e/pageobject/localStorage.ts | 25 +- .../src/client/action-hooks.tsx | 1 - .../src/client/IframeBlockInitializer.tsx | 3 + .../client/block/MapActionInitializers.tsx | 1 - .../src/client/block/MapBlockProvider.tsx | 2 +- .../src/client/components/AMap/Search.tsx | 1 - .../client/components/GoogleMaps/Search.tsx | 1 - .../schema/initializers/BlockInitializers.ts | 1 - .../src/client/TableTransfer.tsx | 2 - .../src/client/index.tsx | 2 +- .../src/client/sequence.tsx | 4 +- .../SnapshotBlockInitializers.tsx | 1 - .../SnapshotOwnerCollectionFieldsSelect.tsx | 2 +- .../antd-token-previewer/ColorPanel.tsx | 1 - .../plugin-workflow/src/client/AddButton.tsx | 10 +- .../plugin-workflow/src/client/Branch.tsx | 5 +- .../src/client/CanvasContent.tsx | 2 +- .../src/client/WorkflowCanvas.tsx | 26 +- .../src/client/WorkflowLink.tsx | 6 +- .../__tests__/e2e/createFormEvent.test.ts | 12 +- .../__tests__/e2e/editFormEvent.test.ts | 16 +- .../__tests__/e2e/pageobject/workFlow.ts | 12 +- .../e2e/triggerCollectionEvent.test.ts | 99 ++++--- .../client/components/CollectionFieldset.tsx | 2 +- .../src/client/components/Duration.tsx | 4 +- .../components/ExecutionStatusSelect.tsx | 8 +- .../src/client/components/FieldsSelect.tsx | 1 - .../hooks/useGetAriaLabelOfAddButton.ts | 20 ++ .../src/client/nodes/condition.tsx | 14 +- .../src/client/nodes/index.tsx | 13 +- .../src/client/nodes/manual/SchemaConfig.tsx | 15 +- .../src/client/nodes/manual/WorkflowTodo.tsx | 6 +- .../src/client/nodes/manual/forms/custom.tsx | 1 - .../src/client/nodes/parallel.tsx | 5 +- .../src/client/schemas/collection.ts | 1 + .../plugin-workflow/src/client/style.tsx | 7 +- .../src/client/triggers/index.tsx | 12 +- .../client/triggers/schedule/EndsByField.tsx | 1 - .../src/client/triggers/schedule/OnField.tsx | 2 - .../client/triggers/schedule/RepeatField.tsx | 2 +- playwright.config.ts | 2 +- scripts/utils.ts | 4 +- vitest.config.ts | 1 + 189 files changed, 1402 insertions(+), 825 deletions(-) create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useGetAriaLabelOfAction.ts create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useGetAriaLabelOfDrawer.ts create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useGetAriaLabelOfModal.ts create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useGetAriaLabelOfPopover.ts create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useSetAriaLabelForDrawer.ts create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useSetAriaLabelForModal.ts create mode 100644 packages/core/client/src/schema-component/antd/action/hooks/useSetAriaLabelForPopover.ts create mode 100644 packages/core/client/src/schema-component/antd/block-item/hooks/useGetAriaLabelOfBlockItem.ts create mode 100644 packages/core/client/src/schema-initializer/hooks/useGetAriaLabelOfSchemaInitializer.ts create mode 100644 packages/core/client/src/schema-settings/hooks/useGetAriaLabelOfDesigner.ts create mode 100644 packages/plugins/@nocobase/plugin-workflow/src/client/hooks/useGetAriaLabelOfAddButton.ts diff --git a/.env.e2e.example b/.env.e2e.example index d4c6a5dfa..1c9702e1c 100644 --- a/.env.e2e.example +++ b/.env.e2e.example @@ -15,6 +15,7 @@ APP_PORT=20000 APP_BASE_URL=http://localhost:20000 APP_KEY=test-key-e2e SOCKET_PATH=storage/gateway-e2e.sock +__E2E__=true API_BASE_PATH=/api/ API_BASE_URL= diff --git a/packages/core/build/src/buildClient.ts b/packages/core/build/src/buildClient.ts index 83e17fdb1..f48cee1b8 100644 --- a/packages/core/build/src/buildClient.ts +++ b/packages/core/build/src/buildClient.ts @@ -36,6 +36,7 @@ export function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean define: { 'process.env.NODE_ENV': JSON.stringify('production'), 'process.env.__TEST__': false, + 'process.env.__E2E__': false, }, build: { minify: false, diff --git a/packages/core/build/src/buildPlugin.ts b/packages/core/build/src/buildPlugin.ts index d22f8b67b..e67cb9225 100644 --- a/packages/core/build/src/buildPlugin.ts +++ b/packages/core/build/src/buildPlugin.ts @@ -1,13 +1,15 @@ -import fs from 'fs-extra'; -import chalk from 'chalk'; import ncc from '@vercel/ncc'; -import path from 'path'; import react from '@vitejs/plugin-react'; +import chalk from 'chalk'; +import fg from 'fast-glob'; +import fs from 'fs-extra'; +import path from 'path'; import { build as tsupBuild } from 'tsup'; import { build as viteBuild } from 'vite'; -import fg from 'fast-glob'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; +import { EsbuildSupportExts, globExcludeFiles } from './constant'; +import { PkgLog, UserConfig, getPackageJson } from './utils'; import { buildCheck, checkFileSize, @@ -18,8 +20,6 @@ import { getSourcePackages, } from './utils/buildPluginUtils'; import { getDepPkgPath, getDepsConfig } from './utils/getDepsConfig'; -import { EsbuildSupportExts, globExcludeFiles } from './constant'; -import { PkgLog, UserConfig, getPackageJson } from './utils'; const validExts = ['.ts', '.tsx', '.js', '.jsx', '.mjs']; const serverGlobalFiles: string[] = ['src/**', '!src/client/**', ...globExcludeFiles]; @@ -304,6 +304,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou define: { 'process.env.NODE_ENV': JSON.stringify('production'), 'process.env.__TEST__': false, + 'process.env.__E2E__': false, }, logLevel: 'warn', build: { diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2165.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2165.test.ts index e4f735796..446f8de42 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2165.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2165.test.ts @@ -280,12 +280,12 @@ const config = { }; // fix https://nocobase.height.app/T-2165 -test.skip('BUG: variable labels should be displayed normally', async ({ page, mockPage }) => { +test('BUG: variable labels should be displayed normally', async ({ page, mockPage }) => { await mockPage(config).goto(); - await page.getByTestId('users-resource').hover(); - await page.getByTestId('designer-schema-settings').first().hover(); - await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByLabel('block-item-CardItem-users-form').hover(); + await page.getByLabel('designer-schema-settings-CardItem-FormV2.Designer-users').hover(); + await page.getByLabel('Linkage rules').click(); await expect(page.getByText('Current form / Nickname')).toBeVisible(); await expect(page.getByText('Current form / Phone')).toBeVisible(); diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2174.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2174.test.ts index e70b3a6a0..6784203f6 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2174.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2174.test.ts @@ -714,9 +714,11 @@ const config = { test('BUG: should show default value option', async ({ page, mockPage }) => { await mockPage(config).goto(); - await page.getByRole('row', { name: '1 View' }).getByText('View').click(); + await page.getByLabel('action-Action.Link-View-view-t_ylz5vtxncxq-table-0').click(); await page.getByText('Single select:').hover(); - await page.getByTestId('t_ylz5vtxncxq.f_nr8xi7ezw5t-field').getByTestId('designer-schema-settings').hover(); + await page + .getByLabel('designer-schema-settings-CollectionField-FormItem.Designer-t_ylz5vtxncxq-t_ylz5vtxncxq.f_nr8xi7ezw5t') + .hover(); - await expect(page.getByRole('menuitem', { name: 'Set default value' })).toBeVisible(); + await expect(page.getByLabel('Set default value')).toBeVisible(); }); diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2176.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2176.test.ts index 467fa936b..24c7850f6 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2176.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2176.test.ts @@ -214,14 +214,12 @@ const config = { }; // fix https://nocobase.height.app/T-2176 -test.skip('BUG: Relationship block issue, menu should not display loading', async ({ page, mockPage }) => { +test('BUG: Relationship block issue, menu should not display loading', async ({ page, mockPage }) => { await mockPage(config).goto(); - await page.getByRole('row', { name: '1 View' }).getByTestId('view-action').click(); - await page.getByTestId('action-drawer').getByRole('button', { name: 'plus Add block' }).hover(); + await page.getByLabel('action-Action.Link-View-view-roles-table-root').click(); + await page.getByLabel('schema-initializer-Grid-RecordBlockInitializers-roles').hover(); + await page.waitForTimeout(300); - // TODO: 这里无论是 not.toBeVisible 还是 toBeVisible 都会通过,暂时不知道怎么解决,先跳过 // 这个输入框不应该显示 - await expect(page.getByPlaceholder('Search and select collection')).not.toBeVisible(); - - await expect(page.getByText('Relationship blocks')).not.toBeVisible(); + await expect(page.getByPlaceholder('Search and select collection')).toBeHidden(); }); diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts index ace3f2b51..f4d524947 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2183.test.ts @@ -163,16 +163,16 @@ const config = { // fix https://nocobase.height.app/T-2183 test('BUG: should save conditions', async ({ page, mockPage }) => { await mockPage(config).goto(); - await page.getByTestId('filter-action').click(); + await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click(); await page.getByText('Add condition', { exact: true }).click(); - await page.getByTestId('antd-cascader').getByLabel('Search').click(); + await page.getByTestId('filter-select-field').getByLabel('Search').click(); await page.getByRole('menuitemcheckbox', { name: 'ID' }).click(); await page.getByRole('button', { name: 'Save conditions' }).click(); await page.reload(); - await page.getByTestId('filter-action').click(); + await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click(); // After refreshing the browser, the set field and operator should still be visible - await expect(page.getByTestId('antd-cascader').getByTitle('ID')).toBeVisible(); - await expect(page.getByText('IDis')).toBeVisible(); + await expect(page.getByTestId('filter-select-field').getByText('ID')).toBeVisible(); + await expect(page.getByTestId('filter-select-operator').getByText('is')).toBeVisible(); }); diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2186.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2186.test.ts index 909b0eac1..b8acdb946 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2186.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2186.test.ts @@ -164,8 +164,8 @@ const config = { test('BUG(Filter): the input box displayed should correspond to the field type', async ({ page, mockPage }) => { await mockPage(config).goto(); - await page.getByTestId('filter-action').click(); - await page.getByTestId('antd-cascader').getByLabel('Search').click(); + await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click(); + await page.getByTestId('filter-select-field').getByLabel('Search').click(); await page.getByRole('menuitemcheckbox', { name: 'ID' }).click(); // 应该显示数字输入框 diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2187.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2187.test.ts index da1af3ed0..b208ed5e2 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2187.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2187.test.ts @@ -362,7 +362,7 @@ test('BUG: in the Duplicate mode, the Roles field should not have a value after }) => { await mockPage(config).goto(); - await page.getByText('Duplicate').click(); - await expect(page.getByTestId('users.nickname-field').getByRole('textbox')).toHaveValue('Super Admin'); - await expect(page.getByText('AdminMemberRoot')).not.toBeVisible(); + await page.getByLabel('action-Action.Link-Duplicate-duplicate-users-table-0').click(); + await expect(page.getByRole('textbox')).toHaveValue('Super Admin'); + await expect(page.getByTestId('select-object-multiple')).toHaveText(''); }); diff --git a/packages/core/client/src/__tests__/e2e/bugs/T-2200.test.ts b/packages/core/client/src/__tests__/e2e/bugs/T-2200.test.ts index 5db1db624..44acd3410 100644 --- a/packages/core/client/src/__tests__/e2e/bugs/T-2200.test.ts +++ b/packages/core/client/src/__tests__/e2e/bugs/T-2200.test.ts @@ -341,30 +341,30 @@ const config = { }; // fix https://nocobase.height.app/T-2200 -test.skip('BUG: should be possible to change the value of the association field normally', async ({ - page, - mockPage, -}) => { +test('BUG: should be possible to change the value of the association field normally', async ({ page, mockPage }) => { await mockPage(config).goto(); - await page.getByTestId('table-block').getByText('Edit').click(); - await expect(page.getByTestId('antd-select').getByText('AdminMemberRoot')).toBeVisible(); + await page.getByLabel('action-Action.Link-Edit-update-users-table-0').click(); + await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByLabel('Member')).toBeVisible(); + await expect(page.getByLabel('Root')).toBeVisible(); - await page.getByTestId('antd-select').getByText('AdminMemberRoot').click(); + await page.getByTestId('select-object-multiple').click(); await page.getByRole('option', { name: 'Member' }).click(); + // 再次点击,关闭下拉框。 + await page.getByTestId('select-object-multiple').click(); - // 点击空白处,关闭下拉框。如不关闭,会挡住按钮,导致鼠标 hover 无效 - await page - .getByTestId('action-drawer') - .locator('div') - .filter({ hasText: 'EditAdd tabRoles:AdminRoot Configure fieldsConfigure actionsAdd block' }) - .nth(2) - .click(); + await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByLabel('Member')).toBeHidden(); + await expect(page.getByLabel('Root')).toBeVisible(); - await expect(page.getByTestId('antd-select').getByText('AdminRoot')).toBeVisible(); + await page.getByLabel('schema-initializer-Grid-FormItemInitializers-users').hover(); + await page.getByLabel('Display collection fields-Nickname').click(); + + await page.mouse.move(200, 0); - await page.getByTestId('configure-fields-button-of-form-item-users').hover(); - await page.getByRole('menuitem', { name: 'Nickname' }).click(); await page.waitForTimeout(200); - await expect(page.getByTestId('antd-select').getByText('AdminRoot')).toBeVisible(); + await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByLabel('Member')).toBeHidden(); + await expect(page.getByLabel('Root')).toBeVisible(); }); diff --git a/packages/core/client/src/__tests__/e2e/checkTableRow.test.ts b/packages/core/client/src/__tests__/e2e/checkTableRow.test.ts index abbedbcb0..742c52784 100644 --- a/packages/core/client/src/__tests__/e2e/checkTableRow.test.ts +++ b/packages/core/client/src/__tests__/e2e/checkTableRow.test.ts @@ -344,9 +344,9 @@ test('check table row', async ({ page, mockPage }) => { await mockPage(config).goto(); await expect(page.getByText('cur vulpes suppellex')).toBeVisible(); - await page.getByTestId('table-index-1').hover(); - await page.getByTestId('table-index-1').getByLabel('').click(); - await page.getByTestId('destroy-action').click(); + await page.getByLabel('table-index-1').hover(); + await page.getByRole('checkbox', { name: 'checkbox' }).click(); + await page.getByLabel('action-Action-Delete-destroy-t_fhdhd0nk7b9-table').click(); // 显示确认弹窗 await expect(page.getByText('Are you sure you want to delete it?')).toBeVisible(); diff --git a/packages/core/client/src/__tests__/e2e/createCollections.test.ts b/packages/core/client/src/__tests__/e2e/createCollections.test.ts index 8016adaaa..9a5d79fee 100644 --- a/packages/core/client/src/__tests__/e2e/createCollections.test.ts +++ b/packages/core/client/src/__tests__/e2e/createCollections.test.ts @@ -247,11 +247,11 @@ test.describe('createCollections', () => { test('quickly create collections', async ({ page, mockPage }) => { await mockPage(pageConfig).goto(); - await page.getByRole('button', { name: 'plus Add block' }).hover(); - await page.getByRole('menuitem', { name: 'table Table right' }).hover(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover(); + await page.getByLabel('dataBlocks-table', { exact: true }).hover(); - await expect(page.getByRole('menuitem', { name: 'collection1' })).toBeVisible(); - await expect(page.getByRole('menuitem', { name: 'collection2' })).toBeVisible(); + await expect(page.getByLabel('dataBlocks-table-collection1')).toBeVisible(); + await expect(page.getByLabel('dataBlocks-table-collection2')).toBeVisible(); }); test('no page, just create collections', async ({ page, createCollections }) => { diff --git a/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts b/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts index df70cc325..1264e0c00 100644 --- a/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts +++ b/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts @@ -4,50 +4,50 @@ test.describe('create data block', () => { test('table', async ({ page, mockPage }) => { await mockPage().goto(); - await page.getByRole('button', { name: 'plus Add block' }).hover(); - await page.getByRole('menuitem', { name: 'table Table right' }).hover(); - await page.getByRole('menuitem', { name: 'Users' }).click(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover(); + await page.getByLabel('dataBlocks-table', { exact: true }).hover(); + await page.getByLabel('dataBlocks-table-Users').click(); - await expect(page.getByRole('columnheader', { name: 'setting Configure columns' })).toBeVisible(); + await expect(page.getByText('Configure columns')).toBeVisible(); }); test('form', async ({ page, mockPage }) => { await mockPage().goto(); - await page.getByRole('button', { name: 'plus Add block' }).hover(); - await page.getByRole('menuitem', { name: 'form Form right' }).first().hover(); - await page.getByRole('menuitem', { name: 'Users' }).click(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover(); + await page.getByLabel('dataBlocks-form', { exact: true }).hover(); + await page.getByLabel('dataBlocks-form-Users').click(); - await expect(page.getByRole('button', { name: 'setting Configure fields' })).toBeVisible(); + await expect(page.getByText('Configure fields')).toBeVisible(); }); test('details', async ({ page, mockPage }) => { await mockPage().goto(); - await page.getByRole('button', { name: 'plus Add block' }).hover(); - await page.getByRole('menuitem', { name: 'table Details right' }).hover(); - await page.getByRole('menuitem', { name: 'Users' }).click(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover(); + await page.getByLabel('dataBlocks-details', { exact: true }).hover(); + await page.getByLabel('dataBlocks-details-Users').click(); - await expect(page.getByRole('button', { name: 'setting Configure fields' })).toBeVisible(); + await expect(page.getByText('Configure fields')).toBeVisible(); }); test('list', async ({ page, mockPage }) => { await mockPage().goto(); - await page.getByRole('button', { name: 'plus Add block' }).hover(); - await page.getByRole('menuitem', { name: 'ordered-list List right' }).hover(); - await page.getByRole('menuitem', { name: 'Users' }).click(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover(); + await page.getByLabel('dataBlocks-List', { exact: true }).hover(); + await page.getByLabel('dataBlocks-List-Users').click(); - await expect(page.getByRole('button', { name: 'setting Configure fields' }).first()).toBeVisible(); + await expect(page.getByText('Configure fields').first()).toBeVisible(); }); test('grid card', async ({ page, mockPage }) => { await mockPage().goto(); - await page.getByRole('button', { name: 'plus Add block' }).hover(); - await page.getByRole('menuitem', { name: 'ordered-list Grid Card right' }).hover(); - await page.getByRole('menuitem', { name: 'Users' }).click(); + await page.getByLabel('schema-initializer-Grid-BlockInitializers').hover(); + await page.getByLabel('dataBlocks-GridCard', { exact: true }).hover(); + await page.getByLabel('dataBlocks-GridCard-Users').click(); - await expect(page.getByRole('button', { name: 'setting Configure fields' }).first()).toBeVisible(); + await expect(page.getByText('Configure fields').first()).toBeVisible(); }); }); diff --git a/packages/core/client/src/__tests__/e2e/menu.test.ts b/packages/core/client/src/__tests__/e2e/menu.test.ts index 2be428827..fc54ffa33 100644 --- a/packages/core/client/src/__tests__/e2e/menu.test.ts +++ b/packages/core/client/src/__tests__/e2e/menu.test.ts @@ -5,19 +5,19 @@ test.describe('menu', () => { await page.goto('/'); await enableToConfig(page); - await page.getByTestId('add-menu-item-button-in-header').hover(); - await page.getByRole('menuitem', { name: 'Page' }).click(); + await page.getByLabel('schema-initializer-Menu-header').hover(); + await page.getByLabel('Page').click(); await page.getByRole('textbox').click(); await page.getByRole('textbox').fill('new page'); await page.getByRole('button', { name: 'OK' }).click(); await page.getByText('new page').click(); - await expect(page.getByTestId('add-block-button-in-page')).toBeVisible(); + await expect(page.getByLabel('schema-initializer-Grid-BlockInitializers')).toBeVisible(); // 删除页面,避免影响其他测试 await page.getByRole('menu').getByText('new page').hover(); - await page.getByTestId('designer-schema-settings').hover(); - await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByLabel('designer-schema-settings-Menu.Item').hover(); + await page.getByLabel('Delete').click(); await page.getByRole('button', { name: 'OK' }).click(); }); }); diff --git a/packages/core/client/src/acl/Configuration/StrategyActions.tsx b/packages/core/client/src/acl/Configuration/StrategyActions.tsx index 884e0f2fb..1590a31ec 100644 --- a/packages/core/client/src/acl/Configuration/StrategyActions.tsx +++ b/packages/core/client/src/acl/Configuration/StrategyActions.tsx @@ -84,7 +84,7 @@ export const StrategyActions = connect((props) => { render: (scope, action) => !action.onNewRecord && ( + + { text ) : ( + {visible && (
setVisible(false)} className={css` position: fixed; diff --git a/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.FilterBlockInitializer.tsx b/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.FilterBlockInitializer.tsx index b9df0b414..66254d749 100644 --- a/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.FilterBlockInitializer.tsx +++ b/packages/core/client/src/schema-component/antd/association-filter/AssociationFilter.FilterBlockInitializer.tsx @@ -79,7 +79,6 @@ export const AssociationFilterFilterBlockInitializer = () => { return ( { return ( { - const resource = schema?.['x-decorator-props']?.['resource']; - if (resource) { - return `${resource}-resource`; - } - const field = schema['x-collection-field']; - if (field) { - return `${field}-field`; - } - return `${schema.name}-item`; -}; +import { useGetAriaLabelOfBlockItem } from './hooks/useGetAriaLabelOfBlockItem'; export const BlockItem: React.FC = (props) => { const { className, children } = useProps(props); const Designer = useDesigner(); - const schema = useFieldSchema(); + const { getAriaLabel } = useGetAriaLabelOfBlockItem(props.name); + return ( { + const fieldSchema = useFieldSchema(); + const compile = useCompile(); + const component = fieldSchema['x-component']; + const collectionField = compile(fieldSchema['x-collection-field']); + let { name: blockName } = useBlockContext() || {}; + // eslint-disable-next-line prefer-const + let { name: collectionName, getField } = useCollection(); + blockName = name || blockName; + + const title = compile(fieldSchema['title']) || compile(getField(fieldSchema.name)?.uiSchema?.title); + + const getAriaLabel = useCallback( + (postfix?: string) => { + postfix = postfix ? `-${postfix}` : ''; + return ['block-item', component, collectionName, blockName, collectionField, title, postfix] + .filter(Boolean) + .join('-'); + }, + [component, collectionName, blockName, collectionField, title], + ); + + return { + getAriaLabel, + }; +}; diff --git a/packages/core/client/src/schema-component/antd/calendar/ViewSelect.tsx b/packages/core/client/src/schema-component/antd/calendar/ViewSelect.tsx index c2cccf4fe..25ed5526e 100644 --- a/packages/core/client/src/schema-component/antd/calendar/ViewSelect.tsx +++ b/packages/core/client/src/schema-component/antd/calendar/ViewSelect.tsx @@ -15,7 +15,7 @@ export const ViewSelect = observer( } = useContext(CalendarToolbarContext); return (
- {views.map((name) => ( {messages[name]} diff --git a/packages/core/client/src/schema-component/antd/card-item/CardItem.tsx b/packages/core/client/src/schema-component/antd/card-item/CardItem.tsx index 1c14f50b1..39264df58 100644 --- a/packages/core/client/src/schema-component/antd/card-item/CardItem.tsx +++ b/packages/core/client/src/schema-component/antd/card-item/CardItem.tsx @@ -5,8 +5,15 @@ import { useSchemaTemplate } from '../../../schema-templates'; import { BlockItem } from '../block-item'; import useStyles from './style'; -export const CardItem: React.FC = (props) => { - const { children, ...restProps } = props; +interface Props { + children?: React.ReactNode; + /** 区块标识 */ + name?: string; + [key: string]: any; +} + +export const CardItem = (props: Props) => { + const { children, name, ...restProps } = props; const template = useSchemaTemplate(); const fieldSchema = useFieldSchema(); const templateKey = fieldSchema['x-template-key']; @@ -14,7 +21,7 @@ export const CardItem: React.FC = (props) => { return wrapSSR( templateKey && !template ? null : ( - + {props.children} diff --git a/packages/core/client/src/schema-component/antd/collection-select/CollectionSelect.tsx b/packages/core/client/src/schema-component/antd/collection-select/CollectionSelect.tsx index 29daf8234..13ec4ebd2 100644 --- a/packages/core/client/src/schema-component/antd/collection-select/CollectionSelect.tsx +++ b/packages/core/client/src/schema-component/antd/collection-select/CollectionSelect.tsx @@ -45,7 +45,9 @@ export const CollectionSelect = connect( const { t } = useTranslation(); return ( + { }, [props.schema]); return ( -
- {component - ? React.createElement(component, { - value: props.value, - collectionField: props.collectionField, - onChange: props?.onChange, - renderSchemaComponent, - }) - : renderSchemaComponent()} -
+ {component + ? React.createElement(component, { + value: props.value, + collectionField: props.collectionField, + onChange: props?.onChange, + renderSchemaComponent, + }) + : renderSchemaComponent()}
); }; diff --git a/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx b/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx index 3cfb7fff7..5a60b0486 100644 --- a/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx +++ b/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx @@ -42,7 +42,7 @@ export const FilterGroup = connect((props) => { } > {remove && !mergedDisabled && ( - + { {'Meet '} ) : null} {!props.disabled && ( - + )} diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index 3f60e13e5..14500788e 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -391,11 +391,10 @@ FormItem.Designer = function Designer() { /> )} {showModeSelect && ( - +
{t('Popup size')} { }, [parsedFilter, params]); return ( - + ); diff --git a/packages/core/client/src/schema-component/antd/input/Input.tsx b/packages/core/client/src/schema-component/antd/input/Input.tsx index 1dfcc0e03..a17547dd2 100644 --- a/packages/core/client/src/schema-component/antd/input/Input.tsx +++ b/packages/core/client/src/schema-component/antd/input/Input.tsx @@ -29,6 +29,8 @@ export const Input: ComposedInput = Object.assign( AntdInput.TextArea, mapProps((props, field) => { return { + role: 'button', + 'aria-label': 'textarea', autoSize: { maxRows: 10, minRows: 3, diff --git a/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx b/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx index c53b8332f..f9491e473 100644 --- a/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/kanban/Kanban.Card.Designer.tsx @@ -1,6 +1,6 @@ import { MenuOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; -import { ISchema, useField, useFieldSchema } from '@formily/react'; +import { ISchema, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; import { Space } from 'antd'; import React from 'react'; @@ -13,6 +13,7 @@ import { useFormItemInitializerFields, } from '../../../schema-initializer/utils'; import { OpenModeSchemaItems } from '../../../schema-items'; +import { useGetAriaLabelOfDesigner } from '../../../schema-settings/hooks/useGetAriaLabelOfDesigner'; const titleCss = css` pointer-events: none; @@ -56,17 +57,17 @@ const gridRowColWrap = (schema: ISchema) => { // }; export const KanbanCardDesigner = (props: any) => { - const { dn, designable } = useDesignable(); + const { designable } = useDesignable(); const { t } = useTranslation(); const api = useAPIClient(); const { refresh } = useDesignable(); - const field = useField(); const fieldSchema = useFieldSchema(); const fields = useFormItemInitializerFields({ readPretty: true, block: 'Kanban', }); const associationFields = useAssociatedFormItemInitializerFields({ readPretty: true, block: 'Kanban' }); + const { getAriaLabel } = useGetAriaLabelOfDesigner(); const items: any = [ { @@ -133,7 +134,13 @@ export const KanbanCardDesigner = (props: any) => { dn.insertBeforeEnd(schema); }} items={items} - component={} + component={ + + } />
diff --git a/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx b/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx index 36f53428b..d1900fa90 100644 --- a/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx +++ b/packages/core/client/src/schema-component/antd/list/List.Decorator.tsx @@ -67,7 +67,7 @@ export const ListBlockProvider = (props) => { }, [parsedFilter, params]); return ( - + ); diff --git a/packages/core/client/src/schema-component/antd/menu/Menu.tsx b/packages/core/client/src/schema-component/antd/menu/Menu.tsx index f760e9424..ebcb573b1 100644 --- a/packages/core/client/src/schema-component/antd/menu/Menu.tsx +++ b/packages/core/client/src/schema-component/antd/menu/Menu.tsx @@ -195,10 +195,14 @@ const HeaderMenu = ({ const items = useMemo(() => { const designerBtn = { key: 'x-designer-button', - disabled: true, style: { padding: '0 8px', order: 9999 }, - label: render({ 'data-testid': 'add-menu-item-button-in-header', style: { background: 'none' } }), + label: render({ + 'aria-label': 'schema-initializer-Menu-header', + 'aria-disabled': false, + style: { background: 'none' }, + }), notdelete: true, + disabled: true, }; const result = getMenuItems(() => { return children; @@ -217,7 +221,12 @@ const HeaderMenu = ({ {...others} className={headerMenuClass} onSelect={(info: any) => { - const s = schema.properties[info.key]; + const s = schema.properties?.[info.key]; + + if (!s) { + return; + } + if (mode === 'mix') { if (s['x-component'] !== 'Menu.SubMenu') { onSelect?.(info); @@ -282,7 +291,8 @@ const SideMenu = ({ key: 'x-designer-button', disabled: true, label: render({ - 'data-testid': 'add-menu-item-button-in-side', + 'aria-label': 'schema-initializer-Menu-side', + 'aria-disabled': false, insert: (s) => { const dn = createDesignable({ t, @@ -481,7 +491,12 @@ Menu.Item = observer( label: ( - + { const { t } = useTranslation(); return ( { const { children, ...others } = props; @@ -33,6 +34,7 @@ export const Page = (props) => { const fieldSchema = useFieldSchema(); const dn = useDesignable(); const { theme } = useGlobalTheme(); + const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer(); // react18 tab 动画会卡顿,所以第一个 tab 时,动画禁用,后面的 tab 才启用 const [hasMounted, setHasMounted] = useState(false); @@ -99,6 +101,7 @@ export const Page = (props) => { tabBarExtraContent={ dn.designable && ( ); diff --git a/packages/core/client/src/schema-component/antd/variable/style.ts b/packages/core/client/src/schema-component/antd/variable/style.ts index f5b3f0419..59ed37382 100644 --- a/packages/core/client/src/schema-component/antd/variable/style.ts +++ b/packages/core/client/src/schema-component/antd/variable/style.ts @@ -61,7 +61,7 @@ export const useStyles = genStyleHook('nb-variable', (token) => { insetInlineStart: 'auto', insetInlineEnd: inputPaddingHorizontalBase, zIndex: 1, - display: 'inline-block', + display: 'none', width: token.fontSizeIcon, height: token.fontSizeIcon, marginTop: -token.fontSizeIcon / 2, @@ -73,7 +73,7 @@ export const useStyles = genStyleHook('nb-variable', (token) => { textTransform: 'none', background: token.colorBgContainer, cursor: 'pointer', - opacity: 0, + opacity: 0.8, transition: `color ${token.motionDurationMid} ease, opacity ${token.motionDurationSlow} ease`, textRendering: 'auto', userSelect: 'none', diff --git a/packages/core/client/src/schema-component/common/sortable-item/SortableItem.tsx b/packages/core/client/src/schema-component/common/sortable-item/SortableItem.tsx index 0702ae904..0f1133d29 100644 --- a/packages/core/client/src/schema-component/common/sortable-item/SortableItem.tsx +++ b/packages/core/client/src/schema-component/common/sortable-item/SortableItem.tsx @@ -38,6 +38,7 @@ export const Sortable = (props: any) => { return React.createElement( component || 'div', { + role: 'none', ...others, className: cx('nb-sortable-designer', props.className), ref: setNodeRef, @@ -118,6 +119,7 @@ export const DragHandler = (props) => { }} {...listeners} {...attributes} + role="none" > {props.children}
diff --git a/packages/core/client/src/schema-initializer/SchemaInitializer.tsx b/packages/core/client/src/schema-initializer/SchemaInitializer.tsx index e400e4a37..5abd55b18 100644 --- a/packages/core/client/src/schema-initializer/SchemaInitializer.tsx +++ b/packages/core/client/src/schema-initializer/SchemaInitializer.tsx @@ -5,12 +5,12 @@ import classNames from 'classnames'; // @ts-ignore import { isEmpty } from 'lodash'; import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { useCollection } from '../collection-manager'; import { useCollectMenuItem, useMenuItem } from '../hooks/useMenuItem'; import { Icon } from '../icon'; import { SchemaComponent, useActionContext } from '../schema-component'; import { useCompile, useDesignable } from '../schema-component/hooks'; import { SearchCollections } from './SearchCollections'; +import { useGetAriaLabelOfSchemaInitializer } from './hooks/useGetAriaLabelOfSchemaInitializer'; import { useStyles } from './style'; import { SchemaInitializerButtonProps, @@ -252,7 +252,7 @@ SchemaInitializer.Button = observer( const { Component: CollectComponent, getMenuItem, clean } = useMenuItem(); const menuItems = useRef([]); const { styles } = useStyles(); - const { name } = useCollection(); + const { getAriaLabel } = useGetAriaLabelOfSchemaInitializer(); const changeMenu = (v: boolean) => { setVisible(v); @@ -268,10 +268,6 @@ SchemaInitializer.Button = observer( return null; } - if (others['data-testid'] && name) { - others['data-testid'] = `${others['data-testid']}-${name}`; - } - const buttonDom = component || ( } @@ -298,6 +301,7 @@ function FinallyButton({ } return ( - )} - - - - - - - -
- - - ); -}); + +
+ {isLinkBtn ? ( + + {loading ? t('Duplicating') : children || t('Duplicate')} + + ) : ( + + )} + + + + + + + +
+
+ + ); + }, + { + displayName: 'DuplicateAction', + }, +); diff --git a/packages/core/client/src/schema-initializer/hooks/useGetAriaLabelOfSchemaInitializer.ts b/packages/core/client/src/schema-initializer/hooks/useGetAriaLabelOfSchemaInitializer.ts new file mode 100644 index 000000000..035736b6f --- /dev/null +++ b/packages/core/client/src/schema-initializer/hooks/useGetAriaLabelOfSchemaInitializer.ts @@ -0,0 +1,25 @@ +import { useFieldSchema } from '@formily/react'; +import { useCallback } from 'react'; +import { useCollection } from '../../collection-manager'; + +/** + * label = 'schema-initializer' + x-component + [x-initializer] + [collectionName] + [postfix] + * @returns + */ + +export const useGetAriaLabelOfSchemaInitializer = () => { + const fieldSchema = useFieldSchema(); + const { name } = useCollection(); + const getAriaLabel = useCallback( + (postfix?: string) => { + const initializer = fieldSchema['x-initializer'] ? `-${fieldSchema['x-initializer']}` : ''; + const collectionName = name ? `-${name}` : ''; + postfix = postfix ? `-${postfix}` : ''; + + return `schema-initializer-${fieldSchema['x-component']}${initializer}${collectionName}${postfix}`; + }, + [fieldSchema, name], + ); + + return { getAriaLabel }; +}; diff --git a/packages/core/client/src/schema-initializer/items/DuplicateActionInitializer.tsx b/packages/core/client/src/schema-initializer/items/DuplicateActionInitializer.tsx index 132aca341..6dcbf805c 100644 --- a/packages/core/client/src/schema-initializer/items/DuplicateActionInitializer.tsx +++ b/packages/core/client/src/schema-initializer/items/DuplicateActionInitializer.tsx @@ -5,7 +5,7 @@ export const DuplicateActionInitializer = (props) => { const schema = { type: 'void', 'x-action': 'duplicate', - 'x-acl-action':'create', + 'x-acl-action': 'create', title: '{{ t("Duplicate") }}', 'x-designer': 'Action.Designer', 'x-component': 'Action.Link', diff --git a/packages/core/client/src/schema-initializer/items/MarkdownBlockInitializer.tsx b/packages/core/client/src/schema-initializer/items/MarkdownBlockInitializer.tsx index 334e15ea9..c90601d7a 100644 --- a/packages/core/client/src/schema-initializer/items/MarkdownBlockInitializer.tsx +++ b/packages/core/client/src/schema-initializer/items/MarkdownBlockInitializer.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import { FormOutlined } from '@ant-design/icons'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { SchemaInitializer } from '../SchemaInitializer'; @@ -16,6 +16,9 @@ export const MarkdownBlockInitializer = (props) => { type: 'void', 'x-designer': 'Markdown.Void.Designer', 'x-decorator': 'CardItem', + 'x-decorator-props': { + name: 'markdown', + }, 'x-component': 'Markdown.Void', 'x-editable': false, 'x-component-props': { diff --git a/packages/core/client/src/schema-initializer/types.ts b/packages/core/client/src/schema-initializer/types.ts index 0ae261367..d23bdffd2 100644 --- a/packages/core/client/src/schema-initializer/types.ts +++ b/packages/core/client/src/schema-initializer/types.ts @@ -51,6 +51,7 @@ interface ItemOptions extends ItemCommonOptions { interface DividerOptions { type: 'divider'; + key?: string; } export type SchemaInitializerItemComponent = (props?: SchemaInitializerItemComponentProps) => any; diff --git a/packages/core/client/src/schema-initializer/utils.ts b/packages/core/client/src/schema-initializer/utils.ts index b0d21e5be..41a56d7c6 100644 --- a/packages/core/client/src/schema-initializer/utils.ts +++ b/packages/core/client/src/schema-initializer/utils.ts @@ -853,7 +853,6 @@ export const useCollectionDataSourceItems = (componentName) => { return [ { - key: 'tableBlock', type: 'itemGroup', title: null, children: [], @@ -1403,6 +1402,7 @@ export const createCollapseBlockSchema = (options) => { associationFilterStyle: { width: '100%', }, + name: 'filter-collapse', }, 'x-designer': 'AssociationFilter.BlockDesigner', 'x-component': 'CardItem', diff --git a/packages/core/client/src/schema-items/OpenModeSchemaItems.tsx b/packages/core/client/src/schema-items/OpenModeSchemaItems.tsx index 33d638126..388286fa7 100644 --- a/packages/core/client/src/schema-items/OpenModeSchemaItems.tsx +++ b/packages/core/client/src/schema-items/OpenModeSchemaItems.tsx @@ -45,11 +45,10 @@ export const OpenModeSchemaItems: React.FC = (options) => { /> ) : null} {openSize && ['modal', 'drawer'].includes(openModeValue) ? ( - +
{t('Popup size')} { - // const divRef = React.useRef(null); - // const shouldUnmount = React.useRef(true); - // const isVisible = React.useRef(false); - // const unmount = useCallback( - // _.debounce(() => { - // if (shouldUnmount.current && !isVisible.current) { - // onShow(false); - // } - // }, 300) as () => void, - // [onShow], - // ); - - // useEffect(() => { - // // 兼容旧浏览器 - // if (!IntersectionObserver) { - // return onShow(true); - // } - - // const observer = new IntersectionObserver((entries) => { - // // 兼容旧浏览器 - // if (entries[0].isIntersecting === undefined) { - // return onShow(true); - // } - - // if (entries[0].isIntersecting) { - // isVisible.current = true; - // onShow(true); - // } else { - // isVisible.current = false; - // unmount(); - // } - // }); - - // observer.observe(divRef.current); - // return () => { - // observer.disconnect(); - // }; - // }, [unmount, onShow]); - - // const onMouseEnter = useCallback(() => { - // shouldUnmount.current = false; - // }, []); - // const onMouseLeave = useCallback(() => { - // shouldUnmount.current = true; - // unmount(); - // }, [unmount]); - - return ( -
- {children} -
- ); -}; - export const GeneralSchemaDesigner = (props: any) => { const { disableInitializer, title, template, draggable = true } = props; const { dn, designable } = useDesignable(); @@ -108,9 +49,7 @@ export const GeneralSchemaDesigner = (props: any) => { const { t } = useTranslation(); const fieldSchema = useFieldSchema(); const compile = useCompile(); - - // TODO: 需要解决弹窗自动关闭的问题:https://nocobase.height.app/T-2100 - const [visible, setVisible] = useState(true); + const { getAriaLabel } = useGetAriaLabelOfDesigner(); const schemaSettingsProps = { dn, @@ -118,8 +57,6 @@ export const GeneralSchemaDesigner = (props: any) => { fieldSchema, }; - const onShow = useCallback((value) => setVisible(value), []); - const rowCtx = useGridRowContext(); const ctx = useGridContext(); const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName) @@ -129,7 +66,13 @@ export const GeneralSchemaDesigner = (props: any) => { return { insertPosition: 'afterEnd', wrap: rowCtx?.cols?.length > 1 ? undefined : gridRowColWrap, - component: , + component: ( + + ), }; }, [rowCtx?.cols?.length]); @@ -138,46 +81,46 @@ export const GeneralSchemaDesigner = (props: any) => { } return ( - - {visible ? ( - <> - {title && ( -
- - {compile(title)} - {template && ( - - {t('Reference template')}: {templateName || t('Untitled')} - - )} - -
+
+ {title && ( +
+ + {compile(title)} + {template && ( + + {t('Reference template')}: {templateName || t('Untitled')} + + )} + +
+ )} +
+ + {draggable && ( + + + )} -
- - {draggable && ( - - - - )} - {!disableInitializer && - (ctx?.InitializerComponent ? ( - - ) : ( - ctx?.renderSchemaInitializer?.(initializerProps) - ))} - - } - {...schemaSettingsProps} - > - {props.children} - - -
- - ) : null} - + {!disableInitializer && + (ctx?.InitializerComponent ? ( + + ) : ( + ctx?.renderSchemaInitializer?.(initializerProps) + ))} + + } + {...schemaSettingsProps} + > + {props.children} + +
+
+
); }; diff --git a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx index ba996a6ea..c214275b8 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx +++ b/packages/core/client/src/schema-settings/LinkageRules/LinkageRuleAction.tsx @@ -72,7 +72,6 @@ export const FormFieldLinkageRuleAction = observer( placeholder={t('Select field')} /> { diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index c8637b7b3..e8d27a943 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -29,6 +29,7 @@ import React, { useMemo, // @ts-ignore useTransition as useReactTransition, + useRef, useState, } from 'react'; import { createPortal } from 'react-dom'; @@ -147,10 +148,10 @@ interface ModalItemProps { type SchemaSettingsNested = { Remove?: React.FC; - Item?: React.FC; + Item?: React.FC; Divider?: React.FC; Popup?: React.FC; - SwitchItem?: React.FC; + SwitchItem?: React.FC; CascaderItem?: React.FC & Omit & { title: any }>; DataScope?: React.FC; ModalItem: React.FC; @@ -242,6 +243,7 @@ SchemaSettings.Template = function Template(props) { if (template) { return ( { const schema = await copyTemplateSchema(template); const removed = tdn.removeWithoutEmit(); @@ -260,6 +262,7 @@ SchemaSettings.Template = function Template(props) { } return ( { setVisible(false); const { title } = collectionName ? getCollection(collectionName) : { title: '' }; @@ -367,6 +370,7 @@ SchemaSettings.FormItemTemplate = function FormItemTemplate(props) { if (template) { return ( { const schema = await copyTemplateSchema(template); const templateSchema = findBlockTemplateSchema(fieldSchema); @@ -403,6 +407,7 @@ SchemaSettings.FormItemTemplate = function FormItemTemplate(props) { } return ( { setVisible(false); const { title } = getCollection(collectionName); @@ -474,15 +479,27 @@ SchemaSettings.FormItemTemplate = function FormItemTemplate(props) { ); }; -SchemaSettings.Item = function Item(props) { +SchemaSettings.Item = function Item(props: { + title: string; + name?: string; + children?: ReactNode; + eventKey?: string; + onClick?: (e: any) => void; +}) { const { pushMenuItem } = useCollectMenuItems(); const { collectMenuItem } = useCollectMenuItem(); - const { eventKey } = props; - const key = useMemo(() => uid(), []); + const { eventKey, title, name } = props; + + if (process.env.NODE_ENV !== 'production' && !title) { + throw new Error('SchemaSettings.Item must have a title'); + } + const item = { - ..._.omit(props, ['children']), - key, - eventKey: (eventKey as any) || key, + role: 'button', + 'aria-label': name || title, + key: title, + ..._.omit(props, ['children', 'name']), + eventKey: eventKey as any, onClick: (info) => { info.domEvent.preventDefault(); info.domEvent.stopPropagation(); @@ -519,6 +536,8 @@ SchemaSettings.SubMenu = function SubMenu(props) { const { pushMenuItem } = useCollectMenuItems(); const key = useMemo(() => uid(), []); const item = { + role: 'button', + 'aria-label': props.title, key, label: props.title, title: props.title, @@ -554,6 +573,7 @@ SchemaSettings.Remove = function Remove(props: any) { return ( { modal.confirm({ @@ -636,6 +656,7 @@ SchemaSettings.ConnectDataBlocks = function ConnectDataBlocks(props: { return ( target.uid === block.uid)} onChange={(checked) => { @@ -666,6 +687,7 @@ SchemaSettings.ConnectDataBlocks = function ConnectDataBlocks(props: { return ( + { - setOpen(false); - _onClick?.(...args); - }; - const onMouseEnter = useCallback(() => setOpen(true), []); - - // 鼠标 hover 时,打开下拉框 - const moreProps = openOnHover - ? { - onMouseEnter, - open, - } - : {}; + const { title, name, options, value, onChange, onClick, ...others } = props; return ( - -
- {title} - { + clearTimeout(timerRef.current); + }} + /> +
+ ); +} + function getFieldDefaultValue(fieldSchema: ISchema, collectionField: CollectionFieldOptions) { const result = fieldSchema?.default ?? collectionField?.defaultValue; return result; diff --git a/packages/core/client/src/schema-settings/hooks/useGetAriaLabelOfDesigner.ts b/packages/core/client/src/schema-settings/hooks/useGetAriaLabelOfDesigner.ts new file mode 100644 index 000000000..00e8ad3ee --- /dev/null +++ b/packages/core/client/src/schema-settings/hooks/useGetAriaLabelOfDesigner.ts @@ -0,0 +1,27 @@ +import { useFieldSchema } from '@formily/react'; +import { useCallback } from 'react'; +import { useCollection } from '../../collection-manager'; + +/** + * label = 'designer' + name + x-component + [x-designer] + [collectionName] + [x-collection-field] + [postfix] + * @returns + */ +export const useGetAriaLabelOfDesigner = () => { + const fieldSchema = useFieldSchema(); + let { name: collectionName } = useCollection(); + const component = fieldSchema['x-component']; + const designer = fieldSchema['x-designer'] ? `-${fieldSchema['x-designer']}` : ''; + const collectionField = fieldSchema['x-collection-field'] ? `-${fieldSchema['x-collection-field']}` : ''; + collectionName = collectionName ? `-${collectionName}` : ''; + + const getAriaLabel = useCallback( + (name: string, postfix?: string) => { + postfix = postfix ? `-${postfix}` : ''; + + return `designer-${name}-${component}${designer}${collectionName}${collectionField}${postfix}`; + }, + [collectionField, collectionName, component, designer], + ); + + return { getAriaLabel }; +}; diff --git a/packages/core/client/src/user/ChangePassword.tsx b/packages/core/client/src/user/ChangePassword.tsx index c3ac598c6..542a4cdcd 100644 --- a/packages/core/client/src/user/ChangePassword.tsx +++ b/packages/core/client/src/user/ChangePassword.tsx @@ -120,6 +120,7 @@ export const useChangePassword = () => { return useMemo(() => { return { + role: 'button', key: 'password', eventKey: 'ChangePassword', onClick: () => { diff --git a/packages/core/client/src/user/CurrentUser.tsx b/packages/core/client/src/user/CurrentUser.tsx index 242a225d1..9752a5362 100644 --- a/packages/core/client/src/user/CurrentUser.tsx +++ b/packages/core/client/src/user/CurrentUser.tsx @@ -17,7 +17,14 @@ const useApplicationVersion = () => { return { key: 'version', disabled: true, - label: `Version ${data?.data?.version}`, + label: ( + {`Version ${data?.data?.version}`} + ), }; }, [data?.data?.version]); }; @@ -69,6 +76,7 @@ export const SettingsMenu: React.FC<{ return [ { + role: 'button', key: 'cache', label: t('Clear cache'), onClick: async () => { @@ -77,6 +85,7 @@ export const SettingsMenu: React.FC<{ }, }, { + role: 'button', key: 'reboot', label: t('Restart application'), onClick: async () => { @@ -120,6 +129,7 @@ export const SettingsMenu: React.FC<{ }, ...controlApp, { + role: 'button', key: 'signout', label: t('Sign out'), onClick: async () => { diff --git a/packages/core/client/src/user/EditProfile.tsx b/packages/core/client/src/user/EditProfile.tsx index 3dba66b72..a08289c17 100644 --- a/packages/core/client/src/user/EditProfile.tsx +++ b/packages/core/client/src/user/EditProfile.tsx @@ -126,6 +126,7 @@ export const useEditProfile = () => { return useMemo(() => { return { + role: 'button', key: 'profile', eventKey: 'EditProfile', onClick: () => { @@ -133,7 +134,7 @@ export const useEditProfile = () => { ctx?.setVisible(false); }, label: ( - <> +
{t('Edit profile')}
e.stopPropagation()}> @@ -143,7 +144,7 @@ export const useEditProfile = () => { />
- +
), }; }, [visible]); diff --git a/packages/core/client/src/user/LanguageSettings.tsx b/packages/core/client/src/user/LanguageSettings.tsx index aa8e77d12..9ed12c93b 100644 --- a/packages/core/client/src/user/LanguageSettings.tsx +++ b/packages/core/client/src/user/LanguageSettings.tsx @@ -12,6 +12,7 @@ export const useLanguageSettings = () => { const enabledLanguages: string[] = data?.data?.enabledLanguages || []; const result = useMemo(() => { return { + role: 'button', key: 'language', eventKey: 'LanguageSettings', onClick: () => { @@ -19,6 +20,7 @@ export const useLanguageSettings = () => { }, label: (
{ > {t('Language')}{' '} { - api.auth.setRole(roleName); - await api.resource('users').setDefaultRole({ values: { roleName } }); - location.reload(); - window.location.reload(); - }} - /> -
- ), + label: , }; }, [api, history, roles]); @@ -50,3 +23,49 @@ export const useSwitchRole = () => { return result; }; + +function SelectWithTitle({ t, roles, api }: { t; roles: any; api: APIClient }) { + const [open, setOpen] = useState(false); + const timerRef = useRef(null); + + return ( +
setOpen((v) => !v)} + onMouseLeave={() => { + timerRef.current = setTimeout(() => { + setOpen(false); + }, 200); + }} + > + {t('Switch role')} + { return ( - + {isCustom ? ( { diff --git a/packages/plugins/@nocobase/plugin-audit-logs/src/client/AuditLogsBlockProvider.tsx b/packages/plugins/@nocobase/plugin-audit-logs/src/client/AuditLogsBlockProvider.tsx index 2c17e68a5..7e7597bdf 100644 --- a/packages/plugins/@nocobase/plugin-audit-logs/src/client/AuditLogsBlockProvider.tsx +++ b/packages/plugins/@nocobase/plugin-audit-logs/src/client/AuditLogsBlockProvider.tsx @@ -19,7 +19,9 @@ export const AuditLogsBlockProvider: React.FC = ({ children, ...restProps }) => }} > - {children} + + {children} + ); diff --git a/packages/plugins/@nocobase/plugin-audit-logs/src/client/initializers/AuditLogsTableActionInitializers.tsx b/packages/plugins/@nocobase/plugin-audit-logs/src/client/initializers/AuditLogsTableActionInitializers.tsx index 559759289..bc7306e1c 100644 --- a/packages/plugins/@nocobase/plugin-audit-logs/src/client/initializers/AuditLogsTableActionInitializers.tsx +++ b/packages/plugins/@nocobase/plugin-audit-logs/src/client/initializers/AuditLogsTableActionInitializers.tsx @@ -1,6 +1,5 @@ // 操作记录表格操作配置 export const AuditLogsTableActionInitializers = { - 'data-testid': 'configure-actions-button-of-audit-logs-table', title: "{{t('Configure actions')}}", icon: 'SettingOutlined', style: { diff --git a/packages/plugins/@nocobase/plugin-charts/src/client/ChartBlockEngineDesigner.tsx b/packages/plugins/@nocobase/plugin-charts/src/client/ChartBlockEngineDesigner.tsx index 0424b1416..481504250 100644 --- a/packages/plugins/@nocobase/plugin-charts/src/client/ChartBlockEngineDesigner.tsx +++ b/packages/plugins/@nocobase/plugin-charts/src/client/ChartBlockEngineDesigner.tsx @@ -58,7 +58,7 @@ const validateJSON = { export const ChartBlockEngineDesigner = () => { const fieldSchema = useFieldSchema(); - const { chartBlockEngineMetaData } = fieldSchema?.['x-component-props']; + const { chartBlockEngineMetaData } = fieldSchema?.['x-component-props'] || {}; return ( @@ -95,6 +95,7 @@ export const ChartBlockEngineDesignerInitializer = (props) => { return ( { FormDialog( { diff --git a/packages/plugins/@nocobase/plugin-charts/src/client/select/CustomSelect.tsx b/packages/plugins/@nocobase/plugin-charts/src/client/select/CustomSelect.tsx index 9633ab374..4d0138036 100644 --- a/packages/plugins/@nocobase/plugin-charts/src/client/select/CustomSelect.tsx +++ b/packages/plugins/@nocobase/plugin-charts/src/client/select/CustomSelect.tsx @@ -21,7 +21,6 @@ const InternalSelect = connect( const group2 = options.filter((option) => option.group === 1); return ( { setCurrent({ schema, field, collection: name, service, data: service?.data }); @@ -92,6 +93,7 @@ ChartRenderer.Designer = function Designer() { {t('Configure')} insertAdjacent('afterEnd', gridRowColWrap(createRendererSchema(schema?.['x-decorator-props'])))} > diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/createLocalStorage.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/createLocalStorage.test.ts index ec3fb1cde..6c4852250 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/createLocalStorage.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/createLocalStorage.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@nocobase/test/client'; import { dayjs } from '@nocobase/utils'; import { CreateLocalStorage } from './pageobject/localStorage'; -test.describe.skip('file manager', () => { +test.describe('file manager', () => { test('add new local storage', async ({ page }) => { //用例编号 const caseNum = 'FM01AA'; @@ -14,29 +14,25 @@ test.describe.skip('file manager', () => { // 2、测试步骤:进入“文件管理器”-“新建”按钮,填写表单,点击“确定”按钮 await page.goto('/admin/settings/file-manager/storages'); await page.waitForLoadState('networkidle'); - await page.getByTestId('create-action').click(); + await page.getByRole('button', { name: 'plus Add new' }).hover(); + await page.getByRole('menuitem', { name: 'Local storage' }).click(); const createLocalStorage = new CreateLocalStorage(page); await createLocalStorage.title.fill(caseTitle); const storageName = caseNum + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); await createLocalStorage.storageName.fill(storageName); await createLocalStorage.storagebaseURL.fill('/storage/uploadsFM01AA'); - await createLocalStorage.storageType.click(); - await page.getByText('Local storage').nth(1).click(); await createLocalStorage.destination.fill('storage/uploadsFM01AA'); // await createLocalStorage.path.fill(''); // await createLocalStorage.defaultStorage.check(); // await createLocalStorage.deleteRecordRetentionFile.check(); - await page.getByTestId('submit-action').click(); + await page.getByLabel('action-Action-Submit-storages').click(); // 3、预期结果:新建成功,列表中出现新建的文件管理器 - await expect(page.getByText(storageName)).toBeAttached(); + await expect(page.getByText(storageName)).toBeVisible(); // await page.waitForTimeout(5000); // 4、后置处理:删除新建的文件管理器 - await page - .getByRole('row', { name: '2 ' + caseTitle + ' ' + storageName + ' Edit Delete' }) - .getByTestId('delete-action') - .click(); + await page.getByLabel(`action-Action.Link-Delete-storages-${storageName}`).click(); await page.getByRole('button', { name: 'OK' }).click(); await expect(page.getByText(storageName)).toBeHidden(); }); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/editLocalStorage.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/editLocalStorage.test.ts index 587bbf9cc..be10d08b7 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/editLocalStorage.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/editLocalStorage.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@nocobase/test/client'; import { dayjs } from '@nocobase/utils'; import { CreateLocalStorage, EditLocalStorage } from './pageobject/localStorage'; -test.describe.skip('File manager', () => { +test.describe('File manager', () => { test('edit local storage title', async ({ page }) => { //用例编号 const caseNum = 'FM02AA'; @@ -12,44 +12,37 @@ test.describe.skip('File manager', () => { // 1、前置条件:1.1已登录;1.2存在一个文件管理器 await page.goto('/admin/settings/file-manager/storages'); await page.waitForLoadState('networkidle'); - await page.getByTestId('create-action').click(); + await page.getByRole('button', { name: 'plus Add new' }).hover(); + await page.getByRole('menuitem', { name: 'Local storage' }).click(); const createLocalStorage = new CreateLocalStorage(page); await createLocalStorage.title.fill(caseTitle); const storageName = caseNum + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); await createLocalStorage.storageName.fill(storageName); await createLocalStorage.storagebaseURL.fill('/storage/uploadsFM02AA'); - await createLocalStorage.storageType.click(); - await page.getByText('Local storage').nth(1).click(); await createLocalStorage.destination.fill('storage/uploadsFM02AA'); // await createLocalStorage.path.fill(''); // await createLocalStorage.defaultStorage.check(); // await createLocalStorage.deleteRecordRetentionFile.check(); - await page.getByTestId('submit-action').click(); - await expect(page.getByText(storageName)).toBeAttached(); + await page.getByLabel('action-Action-Submit-storages').click(); + await expect(page.getByText(storageName)).toBeVisible(); // 2、测试步骤:点击“文件管理器”-“编辑”按钮,编辑标题,点击“确定”按钮 - await page - .getByRole('row', { name: '2 ' + caseTitle + ' ' + storageName + ' Edit Delete' }) - .getByTestId('update-action') - .click(); + await page.getByText('Edit').nth(2).click(); const editLocalStorage = new EditLocalStorage(page); caseTitle = caseTitle + dayjs().format('YYYYMMDDHHmmss.SSS').toString(); await editLocalStorage.title.fill(caseTitle); // await editLocalStorage.path.fill(''); // await editLocalStorage.defaultStorage.check(); // await editLocalStorage.deleteRecordRetentionFile.check(); - await page.getByTestId('submit-action').click(); + await page.getByLabel('action-Action-Submit-storages').click(); // await page.waitForTimeout(5000); // 3、预期结果:编辑成功,列表中出现编辑后的文件管理器 - await expect(page.getByText(caseTitle)).toBeAttached(); + await expect(page.getByText(caseTitle)).toBeVisible(); // 4、后置处理:删除文件管理器 - await page - .getByRole('row', { name: '2 ' + caseTitle + ' ' + storageName + ' Edit Delete' }) - .getByTestId('delete-action') - .click(); + await page.getByLabel(`action-Action.Link-Delete-storages-${storageName}`).click(); await page.getByRole('button', { name: 'OK' }).click(); await expect(page.getByText(storageName)).toBeHidden(); }); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/pageobject/localStorage.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/pageobject/localStorage.ts index 8d3f99126..24bdac1f4 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/pageobject/localStorage.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/__tests__/e2e/pageobject/localStorage.ts @@ -1,11 +1,10 @@ -import type { Page, Locator } from '@nocobase/test/client'; +import type { Locator, Page } from '@nocobase/test/client'; export class CreateLocalStorage { readonly page: Page; title: Locator; storageName: Locator; storagebaseURL: Locator; - storageType: Locator; destination: Locator; path: Locator; defaultStorage: Locator; @@ -13,12 +12,11 @@ export class CreateLocalStorage { constructor(page: Page) { this.page = page; - this.title = page.getByTestId('title-item').getByRole('textbox'); - this.storageName = page.getByTestId('name-item').getByRole('textbox'); - this.storagebaseURL = page.getByTestId('baseUrl-item').getByRole('textbox'); - this.storageType = page.getByTestId('type-item').getByLabel('Search'); - this.destination = page.getByTestId('documentRoot-item').getByRole('textbox'); - this.path = page.getByTestId('path-item').getByRole('textbox'); + this.title = page.getByLabel('block-item-CollectionField-storages-Title').getByRole('textbox'); + this.storageName = page.getByLabel('block-item-CollectionField-storages-Storage name').getByRole('textbox'); + this.storagebaseURL = page.getByLabel('block-item-CollectionField-storages-Storage base URL').getByRole('textbox'); + this.destination = page.getByLabel('block-item-Input-storages-Destination').getByRole('textbox'); + this.path = page.getByLabel('block-item-CollectionField-storages-Path').getByRole('textbox'); this.defaultStorage = page.getByLabel('Default storage'); this.deleteRecordRetentionFile = page.getByLabel('Keep file in storage when destroy record'); } @@ -37,12 +35,11 @@ export class EditLocalStorage { constructor(page: Page) { this.page = page; - this.title = page.getByTestId('title-item').getByRole('textbox'); - this.storageName = page.getByTestId('name-item').getByRole('textbox'); - this.storagebaseURL = page.getByTestId('baseUrl-item').getByRole('textbox'); - this.storageType = page.getByTestId('type-item').getByLabel('Search'); - this.destination = page.getByTestId('documentRoot-item').getByRole('textbox'); - this.path = page.getByTestId('path-item').getByRole('textbox'); + this.title = page.getByLabel('block-item-CollectionField-storages-Title').getByRole('textbox'); + this.storageName = page.getByLabel('block-item-CollectionField-storages-Storage name').getByRole('textbox'); + this.storagebaseURL = page.getByLabel('block-item-CollectionField-storages-Storage base URL').getByRole('textbox'); + this.destination = page.getByLabel('block-item-Input-storages-Destination').getByRole('textbox'); + this.path = page.getByLabel('block-item-CollectionField-storages-Path').getByRole('textbox'); this.defaultStorage = page.getByLabel('Default storage'); this.deleteRecordRetentionFile = page.getByLabel('Keep file in storage when destroy record'); } diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/action-hooks.tsx b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/action-hooks.tsx index 230b4d489..466527be8 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/action-hooks.tsx +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/action-hooks.tsx @@ -36,7 +36,6 @@ export const SourceCollection = observer( return (
{ `} > { removedDataSource.setCategoryFilter(value); @@ -319,7 +318,6 @@ export const TableTransfer = connect((props) => { {t('Shared collections')} + + onChange(shortValues[v].cron)}> + ; + return ({ + role: 'button', + 'aria-label': item.type, key: item.type, label: item.title, type: item.options ? 'subMenu' : null, children: item.options ? item.options.map((option) => ({ + role: 'button', + 'aria-label': option.key, key: option.key, label: option.label, })) @@ -108,7 +114,7 @@ export function AddButton({ upstream, branchIndex = null }: AddButtonProps) { } `} > -
); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/Branch.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/Branch.tsx index cafee6cc8..0e2182812 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/Branch.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/Branch.tsx @@ -1,6 +1,7 @@ import { cx } from '@nocobase/client'; import React from 'react'; import { AddButton } from './AddButton'; +import { useGetAriaLabelOfAddButton } from './hooks/useGetAriaLabelOfAddButton'; import { Node } from './nodes'; import useStyles from './style'; @@ -18,7 +19,7 @@ export function Branch({ className?: string; }) { const { styles } = useStyles(); - + const { getAriaLabel } = useGetAriaLabelOfAddButton(from, branchIndex); const list: any[] = []; for (let node = entry; node; node = node.downstream) { list.push(node); @@ -28,7 +29,7 @@ export function Branch({
{controller} - +
{list.map((item) => ( diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/CanvasContent.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/CanvasContent.tsx index fe6555f77..f02071c59 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/CanvasContent.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/CanvasContent.tsx @@ -4,10 +4,10 @@ import React from 'react'; import { cx, css } from '@nocobase/client'; import { Branch } from './Branch'; +import { useFlowContext } from './FlowContext'; import { lang } from './locale'; import useStyles from './style'; import { TriggerConfig } from './triggers'; -import { useFlowContext } from './FlowContext'; export function CanvasContent({ entry }) { const { styles } = useStyles(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx index 346e397c7..b9820798b 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx @@ -163,6 +163,8 @@ export function WorkflowCanvas() { items: revisions .sort((a, b) => b.id - a.id) .map((item, index) => ({ + role: 'button', + 'aria-label': `version-${index}`, key: `${item.id}`, icon: item.current ? : null, className: cx({ @@ -179,7 +181,7 @@ export function WorkflowCanvas() { })), }} > -
); } @@ -270,7 +272,13 @@ function CalculationGroup({ value, onChange }) { > {'Meet '} - onChange({ ...value, type: t })} + > All Any diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx index 6d26bbf43..185db16fe 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx @@ -21,6 +21,7 @@ import { useFlowContext } from '../FlowContext'; import { DrawerDescription } from '../components/DrawerDescription'; import { StatusButton } from '../components/StatusButton'; import { JobStatusOptionsMap } from '../constants'; +import { useGetAriaLabelOfAddButton } from '../hooks/useGetAriaLabelOfAddButton'; import { lang } from '../locale'; import useStyles from '../style'; import { VariableOption, VariableOptions } from '../variable'; @@ -137,6 +138,7 @@ export function useUpstreamScopes(node) { export function Node({ data }) { const { styles } = useStyles(); + const { getAriaLabel } = useGetAriaLabelOfAddButton(data); const { component: Component = NodeDefaultView, endding } = instructions.get(data.type); return ( @@ -144,7 +146,7 @@ export function Node({ data }) {
{!endding ? ( - + ) : (
-
+
{typeTitle} {data.id}
setEditingTitle(ev.target.value)} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/SchemaConfig.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/SchemaConfig.tsx index cd67bf1d0..b5aa34e1d 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/SchemaConfig.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/SchemaConfig.tsx @@ -167,15 +167,7 @@ function AddBlockButton(props: any) { }, ] as SchemaInitializerItemOptions[]; - return ( - - ); + return ; } function AssignedFieldValues() { @@ -235,7 +227,9 @@ function AssignedFieldValues() { return ( <> - setOpen(true)}>{title} + setOpen(true)}> + {title} + - {children} + + {children} + ); } diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/forms/custom.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/forms/custom.tsx index 1445d7cfa..ca1751ff9 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/forms/custom.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/manual/forms/custom.tsx @@ -221,7 +221,6 @@ function AddCustomFormField(props) { }} > a.branchIndex - b.branchIndex); const [branchCount, setBranchCount] = useState(Math.max(2, branches.length)); + const { getAriaLabel } = useGetAriaLabelOfAddButton(data); const tempBranches = Array(Math.max(0, branchCount - branches.length)).fill(null); const lastBranchHead = branches[branches.length - 1]; @@ -107,6 +109,7 @@ export default { `} >