From a57c93d35b110c92e128984b540a88bb3ac0cc34 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: Wed, 27 Sep 2023 20:00:17 +0800 Subject: [PATCH] feat: support e2e (#2624) * chore: upgrade vitest to v0.34.3 * feat: setup NocoBase * chore: preparing test env * test: add a test of rigster * refactor: rename test dir to testUtils * chore: add tests * chore: add ci for e2e * chore: fix ci * chore: avoid error in CI * chore: add some utils for test * chore: make more stable * chore: should not close server in CI * chore: add comments * chore: change output dir * fix: should use current branch to run tests * chore: should request systemSettings by api in e2e * chore: should build first in e2e CI * chore: remove key * chore: use execa to replace execSync * refactor: extract test suite * chore: add gotoPage * chore: update uid of pageSchema * chore: update collection name * chore: use faker.js to generate data * refactor: extract page config * chore: ignore for association fields in faker * chore: add testid * chore: optimize action designer * chore: associationFilter.Item designer * chore: AssiciationFilter & BlockItem * Revert "chore: AssiciationFilter & BlockItem" This reverts commit b418df650e106fd0c8e23035d2f75acf60dcafe4. * Revert "chore: associationFilter.Item designer" This reverts commit 7aa4d35c1af7f3a780b370d8b1b44aac01697be3. * Revert "chore: optimize action designer" This reverts commit ff717b972ffd64f7968d565a3a84ad617ff889e2. * chore: optimize Designer * chore: compat with older browsers * chore: use describe to avoid hooks is not run * chore: add no-floating-promises to eslint rules * chore: support argv * chore: demo * chore: better testId * chore: change .e2e.ts to .test.ts * fix(SchemaInitializer): avoid error * refactor: move e2eUtils.ts to @nocobase/test * fix: move e2eUtils to client * chore: remove uselesscode * refactor: add .env.e2e.example * chore: optimize log * refactor: use mockPage to replace gotoPage * chore: update env.e2e * chore: add APP_BASE_URL * chore: gitigore * test: add test related of menu * chore: add SOCKET_PATH in env * fix(vscode): load env when using vscode plugin --- .env.e2e.example | 97 ++++ .eslintrc | 6 +- .github/workflows/nocobase-test-e2e.yml | 36 ++ .gitignore | 4 + .vscode/launch.json | 13 + package.json | 6 + .../__tests__/e2e/createCollections.test.ts | 256 ++++++++++ .../src/__tests__/e2e/createDataBlock.test.ts | 53 ++ .../client/src/__tests__/e2e/faker.test.ts | 324 ++++++++++++ .../client/src/__tests__/e2e/menu.test.ts | 23 + .../src/__tests__/e2e/pageSchema.test.ts | 462 +++++++++++++++++ packages/core/client/src/acl/ACLShortcut.tsx | 2 +- .../src/acl/Configuration/StrategyActions.tsx | 1 + .../src/block-provider/BlockProvider.tsx | 2 +- .../block-provider/CalendarBlockProvider.tsx | 2 +- .../block-provider/DetailsBlockProvider.tsx | 2 +- .../FilterFormBlockProvider.tsx | 2 +- .../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 | 2 +- .../Configuration/components/index.tsx | 8 +- .../templates/components/PreviewFields.tsx | 2 + .../core/client/src/pm/PluginManagerLink.tsx | 1 + .../antd/action/Action.Drawer.tsx | 1 + .../antd/action/Action.Modal.tsx | 1 + .../antd/action/Action.Page.tsx | 2 +- .../antd/action/demos/demo5.tsx | 2 +- .../appends-tree-select/AppendsTreeSelect.tsx | 3 +- .../antd/association-field/InternalPicker.tsx | 11 +- .../association-field/InternalSubTable.tsx | 8 +- ...sociationFilter.FilterBlockInitializer.tsx | 1 + .../AssociationFilter.Initializer.tsx | 1 + .../antd/association-select/demos/demo1.tsx | 2 +- .../antd/calendar/ViewSelect.tsx | 2 +- .../collection-select/CollectionSelect.tsx | 1 + .../antd/collection-select/demos/demo1.tsx | 2 +- .../antd/color-select/ColorSelect.tsx | 2 +- .../schema-component/antd/cron/CronSet.tsx | 1 + .../antd/filter/DynamicComponent.tsx | 16 +- .../antd/filter/FilterGroup.tsx | 3 +- .../antd/filter/FilterItem.tsx | 6 +- .../antd/form-item/FormItem.tsx | 1 + .../antd/form-item/demos/demo1.tsx | 2 +- .../antd/form-v2/Templates.tsx | 1 + .../antd/form-v2/demos/demo1.tsx | 2 +- .../antd/form-v2/demos/demo2.tsx | 2 +- .../antd/form-v2/demos/demo3.tsx | 2 +- .../antd/g2plot/demos/demo1.tsx | 2 +- .../antd/grid-card/GridCard.Decorator.tsx | 2 +- .../antd/grid/demos/demo2.tsx | 2 +- .../antd/kanban/demos/demo1.tsx | 2 +- .../antd/list/List.Decorator.tsx | 20 +- .../antd/page/PageTabDesigner.tsx | 5 +- .../antd/record-picker/InputRecordPicker.tsx | 1 + .../antd/record-picker/demos/demo1.tsx | 2 +- .../antd/remote-select/demos/demo1.tsx | 2 +- .../schema-component/antd/select/Select.tsx | 2 + .../antd/table-v2/demos/demo1.tsx | 2 +- .../antd/upload/demos/apiClient.ts | 2 +- .../schema-component/antd/variable/Input.tsx | 1 + .../common/sortable-item/SortableItem.tsx | 9 +- .../schema-initializer/SchemaInitializer.tsx | 10 +- .../buttons/BlockInitializers.tsx | 1 + .../buttons/BulkEditFormItemInitializers.tsx | 1 + .../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 | 4 +- .../buttons/RecordFormBlockInitializers.tsx | 1 + .../buttons/SubTableActionInitializers.tsx | 1 + .../buttons/TableActionInitializers.tsx | 1 + .../buttons/TableColumnInitializers.tsx | 5 +- .../buttons/TableSelectorInitializers.tsx | 1 + .../components/BulkEditField.tsx | 2 +- .../src/schema-items/OpenModeSchemaItems.tsx | 7 +- .../DataTemplates/components/Designer.tsx | 1 + .../schema-settings/GeneralSchemaDesigner.tsx | 154 ++++-- .../LinkageRules/LinkageRuleAction.tsx | 5 +- .../LinkageRules/ValueDynamicComponent.tsx | 6 +- .../src/schema-settings/SchemaSettings.tsx | 3 +- .../SystemSettingsProvider.tsx | 3 - .../AppContextProvider.tsx | 0 .../src/{test => testUtils}/collections.ts | 0 .../client/src/{test => testUtils}/index.ts | 1 - .../{test => testUtils}/mainCollections.ts | 0 .../src/{test => testUtils}/mockAPIClient.ts | 0 packages/core/client/src/user/CurrentUser.tsx | 1 + .../core/client/src/user/LanguageSettings.tsx | 1 + packages/core/client/src/user/SwitchRole.tsx | 1 + ...rrent-user-settings-menu-provider.test.tsx | 2 +- packages/core/test/src/client/e2eUtils.ts | 473 ++++++++++++++++++ packages/core/test/src/client/index.ts | 1 + packages/core/test/src/index.ts | 2 +- .../plugin-api-doc/src/client/Document.tsx | 1 + .../client/Configuration/ExpiresSelect.tsx | 9 +- .../AuditLogsTableActionInitializers.tsx | 1 + .../src/client/__tests__/e2e/auth.test.ts | 34 ++ .../src/client/select/CustomSelect.tsx | 4 +- .../src/client/action-hooks.tsx | 4 +- .../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 | 4 +- .../src/client/sequence.tsx | 4 +- .../SnapshotBlockInitializers.tsx | 3 +- .../SnapshotOwnerCollectionFieldsSelect.tsx | 2 +- .../src/client/__tests__/e2e/demo.test.ts | 0 .../antd-token-previewer/ColorPanel.tsx | 1 + .../client/components/CollectionFieldset.tsx | 1 + .../src/client/components/Duration.tsx | 1 + .../components/ExecutionStatusSelect.tsx | 10 +- .../src/client/components/FieldsSelect.tsx | 1 + .../src/client/nodes/condition.tsx | 3 +- .../src/client/nodes/manual/SchemaConfig.tsx | 11 +- .../src/client/nodes/manual/forms/custom.tsx | 1 + .../client/triggers/schedule/EndsByField.tsx | 3 +- .../src/client/triggers/schedule/OnField.tsx | 2 + .../client/triggers/schedule/RepeatField.tsx | 2 +- playwright.config.ts | 49 ++ scripts/auth.setup.ts | 25 + scripts/codegen.setup.ts | 38 ++ scripts/nocobase.setup.ts | 3 + scripts/runE2e.setup.ts | 21 + scripts/utils.ts | 81 +++ scripts/{setupVitest.ts => vitest.setup.ts} | 0 tsconfig.json | 2 +- vitest.config.ts | 11 +- yarn.lock | 28 +- 143 files changed, 2355 insertions(+), 163 deletions(-) create mode 100644 .env.e2e.example create mode 100644 .github/workflows/nocobase-test-e2e.yml create mode 100644 packages/core/client/src/__tests__/e2e/createCollections.test.ts create mode 100644 packages/core/client/src/__tests__/e2e/createDataBlock.test.ts create mode 100644 packages/core/client/src/__tests__/e2e/faker.test.ts create mode 100644 packages/core/client/src/__tests__/e2e/menu.test.ts create mode 100644 packages/core/client/src/__tests__/e2e/pageSchema.test.ts rename packages/core/client/src/{test => testUtils}/AppContextProvider.tsx (100%) rename packages/core/client/src/{test => testUtils}/collections.ts (100%) rename packages/core/client/src/{test => testUtils}/index.ts (99%) rename packages/core/client/src/{test => testUtils}/mainCollections.ts (100%) rename packages/core/client/src/{test => testUtils}/mockAPIClient.ts (100%) create mode 100644 packages/core/test/src/client/e2eUtils.ts create mode 100644 packages/core/test/src/client/index.ts create mode 100644 packages/plugins/@nocobase/plugin-auth/src/client/__tests__/e2e/auth.test.ts create mode 100644 packages/plugins/@nocobase/plugin-theme-editor/src/client/__tests__/e2e/demo.test.ts create mode 100644 playwright.config.ts create mode 100644 scripts/auth.setup.ts create mode 100644 scripts/codegen.setup.ts create mode 100644 scripts/nocobase.setup.ts create mode 100644 scripts/runE2e.setup.ts create mode 100644 scripts/utils.ts rename scripts/{setupVitest.ts => vitest.setup.ts} (100%) diff --git a/.env.e2e.example b/.env.e2e.example new file mode 100644 index 0000000000..d4c6a5dfa4 --- /dev/null +++ b/.env.e2e.example @@ -0,0 +1,97 @@ +################# DOCKER ################# + +ADMINER_PORT=10101 +DB_MYSQL_PORT=10102 +DB_POSTGRES_PORT=10103 +VERDACCIO_PORT=10104 +# VERDACCIO_URL=http://host.docker.internal:10104/ + +################# NOCOBASE APPLICATION ################# + +# !!! When `APP_ENV=production`, opening http://localhost:13000/ will show "Not Found". +# !!! It is recommended to use nginx to proxy static files. For example https://github.com/nocobase/nocobase/blob/main/docker/nocobase/nocobase.conf +APP_ENV=development +APP_PORT=20000 +APP_BASE_URL=http://localhost:20000 +APP_KEY=test-key-e2e +SOCKET_PATH=storage/gateway-e2e.sock + +API_BASE_PATH=/api/ +API_BASE_URL= + +PROXY_TARGET_URL= + +LOGGER_TRANSPORT= +LOGGER_LEVEL= +LOGGER_BASE_PATH=storage/logs-e2e + +################# DATABASE ################# + +DB_DIALECT=sqlite +DB_STORAGE=storage/db/nocobase-e2e.sqlite +DB_TABLE_PREFIX= +# DB_HOST=localhost +# DB_PORT=5432 +# DB_DATABASE=nocobase-e2e +# DB_USER=nocobase +# DB_PASSWORD=nocobase +# DB_LOGGING=on +# DB_UNDERSCORED=false + +#== SSL CONFIG ==# +# DB_DIALECT_OPTIONS_SSL_CA= +# DB_DIALECT_OPTIONS_SSL_KEY= +# DB_DIALECT_OPTIONS_SSL_CERT= +# DB_DIALECT_OPTIONS_SSL_REJECT_UNAUTHORIZED=true + +################# CACHE ################# +# default is memory cache, when develop mode,code's change will be clear memory cache, so can use 'cache-manager-fs-hash' +# CACHE_CONFIG={"storePackage":"cache-manager-fs-hash","ttl":86400,"max":1000} + +################# STORAGE (Initialization only) ################# + +INIT_ROOT_EMAIL=admin@nocobase.com +INIT_ROOT_PASSWORD=admin123 +INIT_ROOT_NICKNAME=Super Admin +INIT_ROOT_USERNAME=nocobase + +# local or ali-oss +DEFAULT_STORAGE_TYPE=local + +# LOCAL STORAGE +LOCAL_STORAGE_BASE_URL=/storage/uploads-e2e +LOCAL_STORAGE_DEST=storage/uploads-e2e + +# ALI OSS STORAGE +ALI_OSS_STORAGE_BASE_URL= +ALI_OSS_REGION=oss-cn-beijing +ALI_OSS_ACCESS_KEY_ID= +ALI_OSS_ACCESS_KEY_SECRET= +ALI_OSS_BUCKET= + +# Tencent COS STORAGE +TX_COS_STORAGE_BASE_URL= +TX_COS_REGION=ap-guangzhou +TX_COS_SECRET_ID= +TX_COS_SECRET_KEY= +TX_COS_BUCKET= + +# AWS +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_S3_REGION= +AWS_S3_BUCKET= +AWS_S3_STORAGE_BASE_URL= + +# ALI SMS VERIFY CODE CONFIG +INIT_ALI_SMS_ACCESS_KEY= +INIT_ALI_SMS_ACCESS_KEY_SECRET= +INIT_ALI_SMS_ENDPOINT= +INIT_ALI_SMS_VERIFY_CODE_TEMPLATE= +INIT_ALI_SMS_VERIFY_CODE_SIGN= + +# use any string name (no space) +DEFAULT_SMS_VERIFY_CODE_PROVIDER= + +# in nodejs 17+ that SSL v3 causes some ecosystem libraries to become incompatible. Configuring this option can prevent upgrading SSL V3 +# NODE_OPTIONS=--openssl-legacy-provider diff --git a/.eslintrc b/.eslintrc index 09e2b94ad9..7eadc3743e 100755 --- a/.eslintrc +++ b/.eslintrc @@ -38,7 +38,8 @@ "sourceType": "module", "ecmaFeatures": { "jsx": true - } + }, + "project": "./tsconfig.json" }, "rules": { "@typescript-eslint/no-this-alias": "off", @@ -54,6 +55,7 @@ "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-var-requires": "off", - "promise/always-return": "off" + "promise/always-return": "off", + "@typescript-eslint/no-floating-promises": "error" } } diff --git a/.github/workflows/nocobase-test-e2e.yml b/.github/workflows/nocobase-test-e2e.yml new file mode 100644 index 0000000000..939c3fddc5 --- /dev/null +++ b/.github/workflows/nocobase-test-e2e.yml @@ -0,0 +1,36 @@ +name: NocoBase E2E Test + +on: + push: + branches: + - 'main' + - 'develop' + paths: + - '.github/workflows/nocobase-build-test.yml' + - 'packages/**' + pull_request: + branches: + - '**' + paths: + - '.github/workflows/nocobase-build-test.yml' + - 'packages/**' + +jobs: + e2e-test: + strategy: + matrix: + node_version: ['18'] + runs-on: ubuntu-latest + container: node:${{ matrix.node_version }} + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node_version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node_version }} + cache: 'yarn' + - run: yarn install + - run: yarn build + - run: npx playwright install --with-deps + - run: yarn test:e2e + timeout-minutes: 30 diff --git a/.gitignore b/.gitignore index 49af298ae4..a67d4e23d6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ lib/ esm/ es/ .env +.env.e2e .DS_Store yarn-error.log lerna-debug.log @@ -30,4 +31,7 @@ storage/plugins storage/tar storage/tmp storage/app.watch.ts +storage/logs-e2e +storage/uploads-e2e tsconfig.paths.json +playwright diff --git a/.vscode/launch.json b/.vscode/launch.json index 09c8da3ad8..466e078425 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -37,6 +37,19 @@ "runtimeArgs": ["run", "test:client", "${relativeFile}"] } }, + { + "type": "node", + "request": "launch", + "name": "Debug E2E Tests", + "runtimeExecutable": "yarn", + "runtimeArgs": ["test:e2e", "${file}"], + "skipFiles": ["/**"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "windows": { + "runtimeArgs": ["test:e2e", "${fileBasename}"] + } + }, { "type": "node", "request": "launch", diff --git a/package.json b/package.json index a9ebb40335..b06ac8ed31 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,9 @@ "tar": "nocobase tar", "test": "nocobase test", "test:client": "vitest", + "test:e2e": "ts-node-dev ./scripts/runE2e.setup.ts", + "test:codegen": "ts-node-dev ./scripts/codegen.setup.ts", + "test:server": "ts-node-dev ./scripts/nocobase.setup.ts", "tc": "yarn test:client", "doc": "nocobase doc", "postinstall": "nocobase postinstall", @@ -64,6 +67,8 @@ "@commitlint/cli": "^16.1.0", "@commitlint/config-conventional": "^16.0.0", "@commitlint/prompt-cli": "^16.1.0", + "@faker-js/faker": "8.1.0", + "@playwright/test": "^1.37.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", @@ -76,6 +81,7 @@ "dumi-theme-nocobase": "^0.2.14", "eslint-plugin-jest-dom": "^5.0.1", "eslint-plugin-testing-library": "^5.11.0", + "execa": "^5.1.1", "ghooks": "^2.0.4", "jest": "^29.6.2", "jest-cli": "^29.6.2", diff --git a/packages/core/client/src/__tests__/e2e/createCollections.test.ts b/packages/core/client/src/__tests__/e2e/createCollections.test.ts new file mode 100644 index 0000000000..f14d714bf2 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/createCollections.test.ts @@ -0,0 +1,256 @@ +import { expect, test } from '@nocobase/test/client'; + +const pageConfig = { + collections: [ + { + name: 't_wbjenhqk5dz', + title: 'collection1', + inherit: false, + hidden: false, + description: null, + fields: [ + { + name: 'id', + type: 'bigInt', + interface: 'id', + description: null, + collectionName: 't_wbjenhqk5dz', + parentKey: null, + reverseKey: null, + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { + type: 'number', + title: '{{t("ID")}}', + 'x-component': 'InputNumber', + 'x-read-pretty': true, + }, + }, + { + name: 'createdAt', + type: 'date', + interface: 'createdAt', + description: null, + collectionName: 't_wbjenhqk5dz', + parentKey: null, + reverseKey: null, + field: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + { + name: 'createdBy', + type: 'belongsTo', + interface: 'createdBy', + description: null, + collectionName: 't_wbjenhqk5dz', + parentKey: null, + reverseKey: null, + target: 'users', + foreignKey: 'createdById', + uiSchema: { + type: 'object', + title: '{{t("Created by")}}', + 'x-component': 'AssociationField', + 'x-component-props': { + fieldNames: { + value: 'id', + label: 'nickname', + }, + }, + 'x-read-pretty': true, + }, + targetKey: 'id', + }, + { + name: 'updatedAt', + type: 'date', + interface: 'updatedAt', + description: null, + collectionName: 't_wbjenhqk5dz', + parentKey: null, + reverseKey: null, + field: 'updatedAt', + uiSchema: { + type: 'string', + title: '{{t("Last updated at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + { + name: 'updatedBy', + type: 'belongsTo', + interface: 'updatedBy', + description: null, + collectionName: 't_wbjenhqk5dz', + parentKey: null, + reverseKey: null, + target: 'users', + foreignKey: 'updatedById', + uiSchema: { + type: 'object', + title: '{{t("Last updated by")}}', + 'x-component': 'AssociationField', + 'x-component-props': { + fieldNames: { + value: 'id', + label: 'nickname', + }, + }, + 'x-read-pretty': true, + }, + targetKey: 'id', + }, + ], + category: [], + logging: true, + autoGenId: true, + createdBy: true, + updatedBy: true, + createdAt: true, + updatedAt: true, + sortable: true, + template: 'general', + view: false, + }, + { + name: 't_u0c1mmzldgo', + title: 'collection2', + inherit: false, + hidden: false, + description: null, + fields: [ + { + name: 'id', + type: 'bigInt', + interface: 'id', + description: null, + collectionName: 't_u0c1mmzldgo', + parentKey: null, + reverseKey: null, + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { + type: 'number', + title: '{{t("ID")}}', + 'x-component': 'InputNumber', + 'x-read-pretty': true, + }, + }, + { + name: 'createdAt', + type: 'date', + interface: 'createdAt', + description: null, + collectionName: 't_u0c1mmzldgo', + parentKey: null, + reverseKey: null, + field: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + { + name: 'createdBy', + type: 'belongsTo', + interface: 'createdBy', + description: null, + collectionName: 't_u0c1mmzldgo', + parentKey: null, + reverseKey: null, + target: 'users', + foreignKey: 'createdById', + uiSchema: { + type: 'object', + title: '{{t("Created by")}}', + 'x-component': 'AssociationField', + 'x-component-props': { + fieldNames: { + value: 'id', + label: 'nickname', + }, + }, + 'x-read-pretty': true, + }, + targetKey: 'id', + }, + { + name: 'updatedAt', + type: 'date', + interface: 'updatedAt', + description: null, + collectionName: 't_u0c1mmzldgo', + parentKey: null, + reverseKey: null, + field: 'updatedAt', + uiSchema: { + type: 'string', + title: '{{t("Last updated at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + { + name: 'updatedBy', + type: 'belongsTo', + interface: 'updatedBy', + description: null, + collectionName: 't_u0c1mmzldgo', + parentKey: null, + reverseKey: null, + target: 'users', + foreignKey: 'updatedById', + uiSchema: { + type: 'object', + title: '{{t("Last updated by")}}', + 'x-component': 'AssociationField', + 'x-component-props': { + fieldNames: { + value: 'id', + label: 'nickname', + }, + }, + 'x-read-pretty': true, + }, + targetKey: 'id', + }, + ], + category: [], + logging: true, + autoGenId: true, + createdBy: true, + updatedBy: true, + createdAt: true, + updatedAt: true, + sortable: true, + template: 'general', + view: false, + }, + ], +}; + +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 expect(page.getByRole('menuitem', { name: 'collection1' })).toBeVisible(); + await expect(page.getByRole('menuitem', { name: 'collection2' })).toBeVisible(); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts b/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts new file mode 100644 index 0000000000..df70cc325c --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/createDataBlock.test.ts @@ -0,0 +1,53 @@ +import { expect, test } from '@nocobase/test/client'; + +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 expect(page.getByRole('columnheader', { name: 'setting 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 expect(page.getByRole('button', { name: 'setting 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 expect(page.getByRole('button', { name: 'setting 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 expect(page.getByRole('button', { name: 'setting 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 expect(page.getByRole('button', { name: 'setting Configure fields' }).first()).toBeVisible(); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/faker.test.ts b/packages/core/client/src/__tests__/e2e/faker.test.ts new file mode 100644 index 0000000000..37642b26d0 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/faker.test.ts @@ -0,0 +1,324 @@ +import { expect, test } from '@nocobase/test/client'; + +const phonePageConfig = { + collections: [ + { + key: '94kecytzenp', + name: 't_x3mxc1ymorw', + title: 'faker-testing', + inherit: false, + hidden: false, + description: null, + fields: [ + { + key: 'cgzlv8nu6fr', + name: 'id', + type: 'bigInt', + interface: 'id', + description: null, + collectionName: 't_x3mxc1ymorw', + parentKey: null, + reverseKey: null, + autoIncrement: true, + primaryKey: true, + allowNull: false, + uiSchema: { + type: 'number', + title: '{{t("ID")}}', + 'x-component': 'InputNumber', + 'x-read-pretty': true, + }, + }, + { + key: 'sd7xf79138a', + name: 'createdAt', + type: 'date', + interface: 'createdAt', + description: null, + collectionName: 't_x3mxc1ymorw', + parentKey: null, + reverseKey: null, + field: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + { + key: 'z063hvkfdtf', + name: 'createdBy', + type: 'belongsTo', + interface: 'createdBy', + description: null, + collectionName: 't_x3mxc1ymorw', + parentKey: null, + reverseKey: null, + target: 'users', + foreignKey: 'createdById', + uiSchema: { + type: 'object', + title: '{{t("Created by")}}', + 'x-component': 'AssociationField', + 'x-component-props': { + fieldNames: { + value: 'id', + label: 'nickname', + }, + }, + 'x-read-pretty': true, + }, + targetKey: 'id', + }, + { + key: 'wlnvpjkuv3i', + name: 'updatedAt', + type: 'date', + interface: 'updatedAt', + description: null, + collectionName: 't_x3mxc1ymorw', + parentKey: null, + reverseKey: null, + field: 'updatedAt', + uiSchema: { + type: 'string', + title: '{{t("Last updated at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + { + key: 'rxyq48pu0kd', + name: 'updatedBy', + type: 'belongsTo', + interface: 'updatedBy', + description: null, + collectionName: 't_x3mxc1ymorw', + parentKey: null, + reverseKey: null, + target: 'users', + foreignKey: 'updatedById', + uiSchema: { + type: 'object', + title: '{{t("Last updated by")}}', + 'x-component': 'AssociationField', + 'x-component-props': { + fieldNames: { + value: 'id', + label: 'nickname', + }, + }, + 'x-read-pretty': true, + }, + targetKey: 'id', + }, + { + key: '3b24xiumcck', + name: 'f_fyjoexeqvuh', + type: 'string', + interface: 'phone', + description: null, + collectionName: 't_x3mxc1ymorw', + parentKey: null, + reverseKey: null, + uiSchema: { + type: 'string', + 'x-component': 'Input', + 'x-component-props': { + type: 'tel', + }, + title: 'Phone', + }, + }, + ], + category: [], + logging: true, + autoGenId: true, + createdBy: true, + updatedBy: true, + createdAt: true, + updatedAt: true, + sortable: true, + template: 'general', + view: false, + }, + ], + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + zyfy6q68u10: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + sfe29sssqks: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + wr0q46863ri: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + '996h7puslon': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 't_x3mxc1ymorw:list', + 'x-decorator-props': { + collection: 't_x3mxc1ymorw', + resource: 't_x3mxc1ymorw', + 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': 'to6wvr2ymud', + 'x-async': false, + 'x-index': 1, + }, + fmioqg3ac22: { + _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': '7ueb2r7aiq2', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'q6tqbavh1hz', + 'x-async': false, + 'x-index': 1, + }, + '7x5qve01k29': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-designer': 'TableV2.Column.Designer', + 'x-component': 'TableV2.Column', + properties: { + f_fyjoexeqvuh: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 't_x3mxc1ymorw.f_fyjoexeqvuh', + 'x-component': 'CollectionField', + 'x-component-props': {}, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-uid': 'bmewjcb9996', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '6vqo25ezxbr', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'esirxkr0lca', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'zcbhgqtrof5', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'tsma8ix1lun', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'q8bpsoqjz1b', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'edhta7p0qtf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '1o0p25d72br', + 'x-async': true, + 'x-index': 1, + }, +}; + +test.describe('faker', () => { + test('phone', async ({ page, mockPage }) => { + await mockPage(phonePageConfig).goto(); + + await expect(page.getByRole('cell', { name: '14979013912' })).toBeVisible(); + await expect(page.getByRole('cell', { name: '10313363958' })).toBeVisible(); + await expect(page.getByRole('cell', { name: '14365248205' })).toBeVisible(); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/menu.test.ts b/packages/core/client/src/__tests__/e2e/menu.test.ts new file mode 100644 index 0000000000..2be4288271 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/menu.test.ts @@ -0,0 +1,23 @@ +import { enableToConfig, expect, test } from '@nocobase/test/client'; + +test.describe('menu', () => { + test('create new page, then delete', async ({ page }) => { + 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.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 page.getByRole('menu').getByText('new page').hover(); + await page.getByTestId('designer-schema-settings').hover(); + await page.getByRole('menuitem', { name: 'Delete' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + }); +}); diff --git a/packages/core/client/src/__tests__/e2e/pageSchema.test.ts b/packages/core/client/src/__tests__/e2e/pageSchema.test.ts new file mode 100644 index 0000000000..2e9ef7ebe1 --- /dev/null +++ b/packages/core/client/src/__tests__/e2e/pageSchema.test.ts @@ -0,0 +1,462 @@ +import { expect, test } from '@nocobase/test/client'; + +const pageConfig = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + pdxwiogf3pc: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + properties: { + txjj2q9859s: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + w4ziz4txnrn: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + xpfe7mh0n0p: { + 'x-uid': '7pdacls95qk', + _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': [], + 'x-component-props': { + title: 'Table block', + }, + 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': 'wb5mvwm537t', + 'x-async': false, + 'x-index': 1, + }, + xj9bu0sv4yz: { + _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': '33rzky3kemq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7nnw5grzet8', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'm54bsjcv09a', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4yi4kr1wgkz', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'g6bhfeaba55', + 'x-async': false, + 'x-index': 1, + }, + j9xgdusioku: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + '4pdar7yt1d1': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + prtqni04a5t: { + 'x-uid': 'coyws8els85', + _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': { + title: 'Form block', + }, + properties: { + uo3q3bs1l4c: { + _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': '54w1s4gkrp4', + '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': 'wx1ymybhw4d', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '47x9aotslaf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4tdyzz3hh3s', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5pr5x41th9y', + 'x-async': false, + 'x-index': 2, + }, + hlm6jtcx5px: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + '0if9syydrxn': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + fhi0k7tswj1: { + 'x-uid': 'dntzs4ojq9y', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action': 'users:view', + 'x-decorator': 'DetailsBlockProvider', + 'x-decorator-props': { + resource: 'users', + collection: 'users', + readPretty: true, + action: 'list', + params: { + pageSize: 1, + }, + rowKey: 'id', + }, + 'x-designer': 'DetailsDesigner', + 'x-component': 'CardItem', + 'x-component-props': { + title: 'Details block', + }, + properties: { + ru4sjmi898d: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Details', + 'x-read-pretty': true, + 'x-component-props': { + useProps: '{{ useDetailsBlockProps }}', + }, + properties: { + '74jvk36v996': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'DetailsActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + 'x-uid': 'zpt2ewuvd3i', + 'x-async': false, + 'x-index': 1, + }, + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'ReadPrettyFormItemInitializers', + 'x-uid': 'ivgyowpi1rj', + 'x-async': false, + 'x-index': 2, + }, + pagination: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Pagination', + 'x-component-props': { + useProps: '{{ useDetailsPaginationProps }}', + }, + 'x-uid': 'yl3bc57sqmx', + 'x-async': false, + 'x-index': 3, + }, + }, + 'x-uid': 't3sshcjbw53', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'so9f6fq7nmf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'jdrrxpdel86', + 'x-async': false, + 'x-index': 3, + }, + ipq256mnxif: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + h65018f1z8b: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + ym27n2ko5vr: { + 'x-uid': 'j45qq035eas', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action': 'users:view', + 'x-decorator': 'List.Decorator', + 'x-decorator-props': { + resource: 'users', + collection: 'users', + readPretty: true, + action: 'list', + params: { + pageSize: 10, + }, + runWhenParamsChanged: true, + rowKey: 'id', + }, + 'x-component': 'CardItem', + 'x-designer': 'List.Designer', + 'x-component-props': { + title: 'List block', + }, + properties: { + actionBar: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'ListActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-uid': 'c66vjxpf8bf', + 'x-async': false, + 'x-index': 1, + }, + list: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-component': 'List', + 'x-component-props': { + props: '{{ useListBlockProps }}', + }, + properties: { + item: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'object', + 'x-component': 'List.Item', + 'x-read-pretty': true, + 'x-component-props': { + useProps: '{{ useListItemProps }}', + }, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'ReadPrettyFormItemInitializers', + 'x-initializer-props': { + useProps: '{{ useListItemInitializerProps }}', + }, + 'x-uid': 'uqml2dwp9rv', + 'x-async': false, + 'x-index': 1, + }, + actionBar: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-align': 'left', + 'x-initializer': 'ListItemActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + useProps: '{{ useListActionBarProps }}', + layout: 'one-column', + }, + 'x-uid': 'de6lsblu8nf', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'uzulduo8g1z', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 's53c3p9hmf1', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'zg9l4uw1rmy', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'qw9hle324xg', + 'x-async': false, + 'x-index': 4, + }, + }, + 'x-uid': 'lvyncy91yyv', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ny9ds6mjkkj', + 'x-async': true, + 'x-index': 1, + }, +}; + +test.describe('pageSchema', () => { + test('quickly create page schema', async ({ page, mockPage }) => { + await mockPage(pageConfig).goto(); + + await expect(page.getByText('Table block')).toBeVisible(); + await expect(page.getByText('Form block')).toBeVisible(); + await expect(page.getByText('Details block')).toBeVisible(); + await expect(page.getByText('List block')).toBeVisible(); + }); +}); diff --git a/packages/core/client/src/acl/ACLShortcut.tsx b/packages/core/client/src/acl/ACLShortcut.tsx index 3be892b94f..4ef83c3e63 100644 --- a/packages/core/client/src/acl/ACLShortcut.tsx +++ b/packages/core/client/src/acl/ACLShortcut.tsx @@ -16,7 +16,7 @@ const schema2: ISchema = { export const ACLPane = () => { return ( - + ); diff --git a/packages/core/client/src/acl/Configuration/StrategyActions.tsx b/packages/core/client/src/acl/Configuration/StrategyActions.tsx index 9528720ca8..884e0f2fb8 100644 --- a/packages/core/client/src/acl/Configuration/StrategyActions.tsx +++ b/packages/core/client/src/acl/Configuration/StrategyActions.tsx @@ -84,6 +84,7 @@ export const StrategyActions = connect((props) => { render: (scope, action) => !action.onNewRecord && ( + + { text ) : ( {Object.keys(types).map((key) => ( {types[key].title} diff --git a/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx b/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx index 322c84ff72..989908e0e4 100644 --- a/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx +++ b/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx @@ -202,7 +202,7 @@ export const AppendsTreeSelect: React.FC = (props) => { } const { fullTitle } = optionsMap[value] ?? {}; return ( - + {fullTitle?.join(' / ')} ); @@ -219,6 +219,7 @@ export const AppendsTreeSelect: React.FC = (props) => { return ( { const field: any = useField(); - const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext( - RecordPickerContext, - ); + const { + multiple, + options = [], + setSelectedRows, + selectedRows: rcSelectRows = [], + onChange, + } = useContext(RecordPickerContext); const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp(); const { setVisible } = useActionContext(); return { @@ -128,6 +132,7 @@ export const InternalPicker = observer(
, + 'Radio.Group': (props) => , }; return ( 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 66254d749b..b9df0b4144 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,6 +79,7 @@ export const AssociationFilterFilterBlockInitializer = () => { return ( { return ( { 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 25ed5526e9..c2cccf4fed 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/collection-select/CollectionSelect.tsx b/packages/core/client/src/schema-component/antd/collection-select/CollectionSelect.tsx index 2c8d042275..3b39183a7d 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,6 +45,7 @@ export const CollectionSelect = connect( const { t } = useTranslation(); return ( + { }; return ( - {component - ? React.createElement(component, { - value: props.value, - onChange: props?.onChange, - renderSchemaComponent, - }) - : renderSchemaComponent()} +
+ {component + ? React.createElement(component, { + value: props.value, + 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 fa35b8b1ac..3cfb7fff74 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 '} {!operator?.noValue ? : null} {!props.disabled && ( - + remove()} style={{ color: '#bfbfbf' }} /> )} 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 ecbdb786e1..07bea8085e 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 @@ -462,6 +462,7 @@ FormItem.Designer = function Designer() {
{t('Popup size')} { export const GridCardBlockProvider = (props) => { return ( - + ); diff --git a/packages/core/client/src/schema-component/antd/grid/demos/demo2.tsx b/packages/core/client/src/schema-component/antd/grid/demos/demo2.tsx index 52b01236fc..c12e0fb66a 100644 --- a/packages/core/client/src/schema-component/antd/grid/demos/demo2.tsx +++ b/packages/core/client/src/schema-component/antd/grid/demos/demo2.tsx @@ -12,7 +12,7 @@ import { } from '@nocobase/client'; import React from 'react'; -import { mockAPIClient } from '../../../../test'; +import { mockAPIClient } from '../../../../testUtils'; const { apiClient, mockRequest } = mockAPIClient(); mockRequest.onGet('/auth:check').reply(() => { diff --git a/packages/core/client/src/schema-component/antd/kanban/demos/demo1.tsx b/packages/core/client/src/schema-component/antd/kanban/demos/demo1.tsx index 89ca547e6e..c248a501cd 100644 --- a/packages/core/client/src/schema-component/antd/kanban/demos/demo1.tsx +++ b/packages/core/client/src/schema-component/antd/kanban/demos/demo1.tsx @@ -9,7 +9,7 @@ import { SchemaComponentProvider, } from '@nocobase/client'; import React from 'react'; -import { mockAPIClient } from '../../../../test'; +import { mockAPIClient } from '../../../../testUtils'; import collections from './collections'; import data from './data'; 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 23e311785d..1bbdcf51ea 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 @@ -33,16 +33,14 @@ const InternalListBlockProvider = (props) => {
{props.children}
@@ -54,7 +52,7 @@ const InternalListBlockProvider = (props) => { export const ListBlockProvider = (props) => { return ( - + ); diff --git a/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx b/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx index 0c77e64aeb..a8e2160797 100644 --- a/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx +++ b/packages/core/client/src/schema-component/antd/page/PageTabDesigner.tsx @@ -20,7 +20,10 @@ export const PageDesigner = ({ title }) => {
- }> + } + > = (props: IRecordPickerProps) => { /> ) : ( = observer( export const DragHandler = (props) => { const { draggable } = useContext(SortableContext); - const { isDragging, attributes, listeners, setNodeRef, transform } = draggable; - const style = transform - ? { - transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, - } - : undefined; + const { attributes, listeners, setNodeRef } = draggable; return (
{ zIndex: 1, // backgroundColor: '#333', lineHeight: 0, - height: 2, - width: 2, fontSize: 0, display: 'inline-block', }} diff --git a/packages/core/client/src/schema-initializer/SchemaInitializer.tsx b/packages/core/client/src/schema-initializer/SchemaInitializer.tsx index f54086be95..66cdb290c9 100644 --- a/packages/core/client/src/schema-initializer/SchemaInitializer.tsx +++ b/packages/core/client/src/schema-initializer/SchemaInitializer.tsx @@ -5,6 +5,7 @@ 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'; @@ -251,6 +252,7 @@ SchemaInitializer.Button = observer( const { Component: CollectComponent, getMenuItem, clean } = useMenuItem(); const menuItems = useRef([]); const { styles } = useStyles(); + const { name } = useCollection(); const changeMenu = (v: boolean) => { setVisible(v); @@ -266,6 +268,10 @@ SchemaInitializer.Button = observer( return null; } + if (others['data-testid'] && name) { + others['data-testid'] = `${others['data-testid']}-${name}`; + } + const buttonDom = component || (