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
This commit is contained in:
parent
ce879d2dda
commit
a57c93d35b
97
.env.e2e.example
Normal file
97
.env.e2e.example
Normal file
@ -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
|
@ -38,7 +38,8 @@
|
|||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true
|
"jsx": true
|
||||||
}
|
},
|
||||||
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-this-alias": "off",
|
"@typescript-eslint/no-this-alias": "off",
|
||||||
@ -54,6 +55,7 @@
|
|||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
"promise/always-return": "off"
|
"promise/always-return": "off",
|
||||||
|
"@typescript-eslint/no-floating-promises": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
.github/workflows/nocobase-test-e2e.yml
vendored
Normal file
36
.github/workflows/nocobase-test-e2e.yml
vendored
Normal file
@ -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
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -3,6 +3,7 @@ lib/
|
|||||||
esm/
|
esm/
|
||||||
es/
|
es/
|
||||||
.env
|
.env
|
||||||
|
.env.e2e
|
||||||
.DS_Store
|
.DS_Store
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
lerna-debug.log
|
lerna-debug.log
|
||||||
@ -30,4 +31,7 @@ storage/plugins
|
|||||||
storage/tar
|
storage/tar
|
||||||
storage/tmp
|
storage/tmp
|
||||||
storage/app.watch.ts
|
storage/app.watch.ts
|
||||||
|
storage/logs-e2e
|
||||||
|
storage/uploads-e2e
|
||||||
tsconfig.paths.json
|
tsconfig.paths.json
|
||||||
|
playwright
|
||||||
|
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
@ -37,6 +37,19 @@
|
|||||||
"runtimeArgs": ["run", "test:client", "${relativeFile}"]
|
"runtimeArgs": ["run", "test:client", "${relativeFile}"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug E2E Tests",
|
||||||
|
"runtimeExecutable": "yarn",
|
||||||
|
"runtimeArgs": ["test:e2e", "${file}"],
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"internalConsoleOptions": "neverOpen",
|
||||||
|
"windows": {
|
||||||
|
"runtimeArgs": ["test:e2e", "${fileBasename}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
"tar": "nocobase tar",
|
"tar": "nocobase tar",
|
||||||
"test": "nocobase test",
|
"test": "nocobase test",
|
||||||
"test:client": "vitest",
|
"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",
|
"tc": "yarn test:client",
|
||||||
"doc": "nocobase doc",
|
"doc": "nocobase doc",
|
||||||
"postinstall": "nocobase postinstall",
|
"postinstall": "nocobase postinstall",
|
||||||
@ -64,6 +67,8 @@
|
|||||||
"@commitlint/cli": "^16.1.0",
|
"@commitlint/cli": "^16.1.0",
|
||||||
"@commitlint/config-conventional": "^16.0.0",
|
"@commitlint/config-conventional": "^16.0.0",
|
||||||
"@commitlint/prompt-cli": "^16.1.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/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
@ -76,6 +81,7 @@
|
|||||||
"dumi-theme-nocobase": "^0.2.14",
|
"dumi-theme-nocobase": "^0.2.14",
|
||||||
"eslint-plugin-jest-dom": "^5.0.1",
|
"eslint-plugin-jest-dom": "^5.0.1",
|
||||||
"eslint-plugin-testing-library": "^5.11.0",
|
"eslint-plugin-testing-library": "^5.11.0",
|
||||||
|
"execa": "^5.1.1",
|
||||||
"ghooks": "^2.0.4",
|
"ghooks": "^2.0.4",
|
||||||
"jest": "^29.6.2",
|
"jest": "^29.6.2",
|
||||||
"jest-cli": "^29.6.2",
|
"jest-cli": "^29.6.2",
|
||||||
|
256
packages/core/client/src/__tests__/e2e/createCollections.test.ts
Normal file
256
packages/core/client/src/__tests__/e2e/createCollections.test.ts
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
324
packages/core/client/src/__tests__/e2e/faker.test.ts
Normal file
324
packages/core/client/src/__tests__/e2e/faker.test.ts
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
23
packages/core/client/src/__tests__/e2e/menu.test.ts
Normal file
23
packages/core/client/src/__tests__/e2e/menu.test.ts
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
462
packages/core/client/src/__tests__/e2e/pageSchema.test.ts
Normal file
462
packages/core/client/src/__tests__/e2e/pageSchema.test.ts
Normal file
@ -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();
|
||||||
|
});
|
||||||
|
});
|
@ -16,7 +16,7 @@ const schema2: ISchema = {
|
|||||||
|
|
||||||
export const ACLPane = () => {
|
export const ACLPane = () => {
|
||||||
return (
|
return (
|
||||||
<Card bordered={false}>
|
<Card data-testid="acl-pane-card" bordered={false}>
|
||||||
<SchemaComponent components={components} schema={schema2} />
|
<SchemaComponent components={components} schema={schema2} />
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -84,6 +84,7 @@ export const StrategyActions = connect((props) => {
|
|||||||
render: (scope, action) =>
|
render: (scope, action) =>
|
||||||
!action.onNewRecord && (
|
!action.onNewRecord && (
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
value={scope}
|
value={scope}
|
||||||
|
@ -289,7 +289,7 @@ export const BlockProvider = (props) => {
|
|||||||
<BlockRequestProvider {...props} updateAssociationValues={updateAssociationValues} params={params}>
|
<BlockRequestProvider {...props} updateAssociationValues={updateAssociationValues} params={params}>
|
||||||
<SharedFilterProvider {...props} params={params}>
|
<SharedFilterProvider {...props} params={params}>
|
||||||
<FilterBlockRecord {...props} params={params}>
|
<FilterBlockRecord {...props} params={params}>
|
||||||
{props.children}
|
<div data-testid={props['data-testid']}>{props.children}</div>
|
||||||
</FilterBlockRecord>
|
</FilterBlockRecord>
|
||||||
</SharedFilterProvider>
|
</SharedFilterProvider>
|
||||||
</BlockRequestProvider>
|
</BlockRequestProvider>
|
||||||
|
@ -33,7 +33,7 @@ const InternalCalendarBlockProvider = (props) => {
|
|||||||
|
|
||||||
export const CalendarBlockProvider = (props) => {
|
export const CalendarBlockProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<BlockProvider {...props} params={{ ...props.params, paginate: false }}>
|
<BlockProvider data-testid="calendar-block" {...props} params={{ ...props.params, paginate: false }}>
|
||||||
<InternalCalendarBlockProvider {...props} />
|
<InternalCalendarBlockProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
);
|
);
|
||||||
|
@ -39,7 +39,7 @@ const InternalDetailsBlockProvider = (props) => {
|
|||||||
|
|
||||||
export const DetailsBlockProvider = (props) => {
|
export const DetailsBlockProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<BlockProvider {...props}>
|
<BlockProvider data-testid="details-block" {...props}>
|
||||||
<InternalDetailsBlockProvider {...props} />
|
<InternalDetailsBlockProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import { FormBlockProvider } from './FormBlockProvider';
|
|||||||
export const FilterFormBlockProvider = (props) => {
|
export const FilterFormBlockProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<DatePickerProvider value={{ utc: false }}>
|
<DatePickerProvider value={{ utc: false }}>
|
||||||
<FormBlockProvider {...props}></FormBlockProvider>
|
<FormBlockProvider data-testid="filter-form-block" {...props}></FormBlockProvider>
|
||||||
</DatePickerProvider>
|
</DatePickerProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -82,7 +82,7 @@ export const FormBlockProvider = (props) => {
|
|||||||
(currentCollection.name === (collection?.name || collection) && !isEmptyRecord) || !currentCollection.name;
|
(currentCollection.name === (collection?.name || collection) && !isEmptyRecord) || !currentCollection.name;
|
||||||
return (
|
return (
|
||||||
(detailFlag || createFlag || isCusomeizeCreate) && (
|
(detailFlag || createFlag || isCusomeizeCreate) && (
|
||||||
<BlockProvider {...props} block={'form'}>
|
<BlockProvider data-testid={props['data-testid'] || 'form-block'} {...props} block={'form'}>
|
||||||
<InternalFormBlockProvider {...props} />
|
<InternalFormBlockProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
)
|
)
|
||||||
|
@ -77,7 +77,7 @@ export const WithoutFormFieldResource = createContext(null);
|
|||||||
export const FormFieldProvider = (props) => {
|
export const FormFieldProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<WithoutFormFieldResource.Provider value={false}>
|
<WithoutFormFieldResource.Provider value={false}>
|
||||||
<BlockProvider block={'FormField'} {...props}>
|
<BlockProvider data-testid="form-field-block" block={'FormField'} {...props}>
|
||||||
<InternalFormFieldProvider {...props} />
|
<InternalFormFieldProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
</WithoutFormFieldResource.Provider>
|
</WithoutFormFieldResource.Provider>
|
||||||
|
@ -73,7 +73,7 @@ const InternalGanttBlockProvider = (props) => {
|
|||||||
export const GanttBlockProvider = (props) => {
|
export const GanttBlockProvider = (props) => {
|
||||||
const params = { filter: props.params.filter, tree: true, paginate: false, sort: props.fieldNames.start };
|
const params = { filter: props.params.filter, tree: true, paginate: false, sort: props.fieldNames.start };
|
||||||
return (
|
return (
|
||||||
<BlockProvider {...props} params={params}>
|
<BlockProvider data-testid="gantt-block" {...props} params={params}>
|
||||||
<TableBlockProvider {...props} params={params}>
|
<TableBlockProvider {...props} params={params}>
|
||||||
<InternalGanttBlockProvider {...props} />
|
<InternalGanttBlockProvider {...props} />
|
||||||
</TableBlockProvider>
|
</TableBlockProvider>
|
||||||
|
@ -108,7 +108,7 @@ export const KanbanBlockProvider = (props) => {
|
|||||||
params['appends'] = appends;
|
params['appends'] = appends;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<BlockProvider {...props} params={params}>
|
<BlockProvider data-testid="kanban-block" {...props} params={params}>
|
||||||
<InternalKanbanBlockProvider {...props} params={params} />
|
<InternalKanbanBlockProvider {...props} params={params} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
);
|
);
|
||||||
|
@ -97,7 +97,7 @@ export const TableBlockProvider = (props) => {
|
|||||||
return (
|
return (
|
||||||
<SchemaComponentOptions scope={{ treeTable }}>
|
<SchemaComponentOptions scope={{ treeTable }}>
|
||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
<BlockProvider {...props} params={params} runWhenParamsChanged>
|
<BlockProvider data-testid="table-block" {...props} params={params} runWhenParamsChanged>
|
||||||
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={params} />
|
<InternalTableBlockProvider {...props} childrenColumnName={childrenColumnName} params={params} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
|
@ -126,7 +126,7 @@ export const WithoutTableFieldResource = createContext(null);
|
|||||||
export const TableFieldProvider = (props) => {
|
export const TableFieldProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<WithoutTableFieldResource.Provider value={false}>
|
<WithoutTableFieldResource.Provider value={false}>
|
||||||
<BlockProvider block={'TableField'} {...props}>
|
<BlockProvider data-testid="table-field-block" block={'TableField'} {...props}>
|
||||||
<InternalTableFieldProvider {...props} />
|
<InternalTableFieldProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
</WithoutTableFieldResource.Provider>
|
</WithoutTableFieldResource.Provider>
|
||||||
|
@ -238,7 +238,7 @@ export const TableSelectorProvider = (props: TableSelectorProviderProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaComponentOptions scope={{ treeTable }}>
|
<SchemaComponentOptions scope={{ treeTable }}>
|
||||||
<BlockProvider {...props} params={params}>
|
<BlockProvider data-testid="table-selector-block" {...props} params={params}>
|
||||||
<InternalTableSelectorProvider {...props} params={params} extraFilter={extraFilter} />
|
<InternalTableSelectorProvider {...props} params={params} extraFilter={extraFilter} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
</SchemaComponentOptions>
|
</SchemaComponentOptions>
|
||||||
|
@ -17,6 +17,7 @@ export const SourceForeignKey = observer(
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
allowClear
|
allowClear
|
||||||
placeholder={'留空时,自动生成 FK 字段'}
|
placeholder={'留空时,自动生成 FK 字段'}
|
||||||
disabled={field.disabled}
|
disabled={field.disabled}
|
||||||
@ -44,6 +45,7 @@ export const ThroughForeignKey = observer(
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
allowClear
|
allowClear
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
placeholder={'留空时,自动生成 FK 字段'}
|
placeholder={'留空时,自动生成 FK 字段'}
|
||||||
@ -72,6 +74,7 @@ export const TargetForeignKey = observer(
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
allowClear
|
allowClear
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
placeholder={'留空时,自动生成 FK 字段'}
|
placeholder={'留空时,自动生成 FK 字段'}
|
||||||
@ -101,6 +104,7 @@ export const SourceCollection = observer(
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
disabled
|
disabled
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
value={collection.name}
|
value={collection.name}
|
||||||
@ -116,7 +120,7 @@ export const SourceKey = observer(
|
|||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Select disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} />
|
<Select data-testid="antd-select" disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -127,7 +131,7 @@ export const TargetKey = observer(
|
|||||||
() => {
|
() => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Select disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} />
|
<Select data-testid="antd-select" disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -148,6 +148,7 @@ const PreviewCom = (props) => {
|
|||||||
<Tag>{text}</Tag>
|
<Tag>{text}</Tag>
|
||||||
) : (
|
) : (
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
defaultValue={text}
|
defaultValue={text}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
@ -173,6 +174,7 @@ const PreviewCom = (props) => {
|
|||||||
text
|
text
|
||||||
) : (
|
) : (
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
defaultValue={text}
|
defaultValue={text}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
|
@ -67,6 +67,7 @@ export const SettingsCenterDropdown = () => {
|
|||||||
<ActionContextProvider value={{ visible, setVisible }}>
|
<ActionContextProvider value={{ visible, setVisible }}>
|
||||||
<Dropdown placement="bottom" menu={menu}>
|
<Dropdown placement="bottom" menu={menu}>
|
||||||
<Button
|
<Button
|
||||||
|
data-testid="settings-center-button"
|
||||||
icon={<SettingOutlined />}
|
icon={<SettingOutlined />}
|
||||||
// title={t('All plugin settings')}
|
// title={t('All plugin settings')}
|
||||||
/>
|
/>
|
||||||
|
@ -28,6 +28,7 @@ export const ActionDrawer: ComposedActionDrawer = observer(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
|
data-testid="action-drawer"
|
||||||
width={openSizeWidthMap.get(openSize)}
|
width={openSizeWidthMap.get(openSize)}
|
||||||
title={field.title}
|
title={field.title}
|
||||||
{...others}
|
{...others}
|
||||||
|
@ -26,6 +26,7 @@ export const ActionModal: ComposedActionDrawer<ModalProps> = observer(
|
|||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
data-testid="action-modal"
|
||||||
width={actualWidth}
|
width={actualWidth}
|
||||||
title={field.title}
|
title={field.title}
|
||||||
{...(others as ModalProps)}
|
{...(others as ModalProps)}
|
||||||
|
@ -28,7 +28,7 @@ export const ActionPage: ComposedActionDrawer = observer(
|
|||||||
{containerRef?.current &&
|
{containerRef?.current &&
|
||||||
visible &&
|
visible &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div className="nb-action-page">
|
<div data-testid="action-page" className="nb-action-page">
|
||||||
<RecursionField
|
<RecursionField
|
||||||
basePath={field.address}
|
basePath={field.address}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
@ -61,7 +61,7 @@ function TypeSelect(props) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select {...props}>
|
<Select data-testid="antd-select" {...props}>
|
||||||
{Object.keys(types).map((key) => (
|
{Object.keys(types).map((key) => (
|
||||||
<Select.Option key={key} value={key}>
|
<Select.Option key={key} value={key}>
|
||||||
{types[key].title}
|
{types[key].title}
|
||||||
|
@ -202,7 +202,7 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
}
|
}
|
||||||
const { fullTitle } = optionsMap[value] ?? {};
|
const { fullTitle } = optionsMap[value] ?? {};
|
||||||
return (
|
return (
|
||||||
<Tag closable={closable && !disabled} onClose={onClose}>
|
<Tag data-testid="antd-tag" closable={closable && !disabled} onClose={onClose}>
|
||||||
{fullTitle?.join(' / ')}
|
{fullTitle?.join(' / ')}
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
@ -219,6 +219,7 @@ export const AppendsTreeSelect: React.FC<AppendsTreeSelectProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
|
data-testid="antd-tree-select"
|
||||||
value={filteredValue}
|
value={filteredValue}
|
||||||
placeholder={t('Select field')}
|
placeholder={t('Select field')}
|
||||||
showCheckedStrategy={TreeSelect.SHOW_ALL}
|
showCheckedStrategy={TreeSelect.SHOW_ALL}
|
||||||
|
@ -23,9 +23,13 @@ import { flatData, getLabelFormatValue, useLabelUiSchema } from './util';
|
|||||||
|
|
||||||
const useTableSelectorProps = () => {
|
const useTableSelectorProps = () => {
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const { multiple, options = [], setSelectedRows, selectedRows: rcSelectRows = [], onChange } = useContext(
|
const {
|
||||||
RecordPickerContext,
|
multiple,
|
||||||
);
|
options = [],
|
||||||
|
setSelectedRows,
|
||||||
|
selectedRows: rcSelectRows = [],
|
||||||
|
onChange,
|
||||||
|
} = useContext(RecordPickerContext);
|
||||||
const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
|
const { onRowSelectionChange, rowKey = 'id', ...others } = useTsp();
|
||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
return {
|
return {
|
||||||
@ -128,6 +132,7 @@ export const InternalPicker = observer(
|
|||||||
<Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
|
<Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
{...others}
|
{...others}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { FormLayout } from '@formily/antd-v5';
|
import { FormLayout } from '@formily/antd-v5';
|
||||||
import { RecursionField, observer, useField, useFieldSchema, SchemaOptionsContext } from '@formily/react';
|
import { RecursionField, SchemaOptionsContext, observer, useField, useFieldSchema } from '@formily/react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
|
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
|
||||||
import { CollectionProvider } from '../../../collection-manager';
|
import { CollectionProvider } from '../../../collection-manager';
|
||||||
import { useSchemaOptionsContext } from '../../../schema-component';
|
import { useSchemaOptionsContext } from '../../../schema-component';
|
||||||
import Select from '../select/Select';
|
import Select from '../select/Select';
|
||||||
import { useAssociationFieldContext, useInsertSchema } from './hooks';
|
import { useAssociationFieldContext, useInsertSchema } from './hooks';
|
||||||
import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl';
|
|
||||||
import schema from './schema';
|
import schema from './schema';
|
||||||
|
|
||||||
export const InternalSubTable = observer(
|
export const InternalSubTable = observer(
|
||||||
@ -24,8 +24,8 @@ export const InternalSubTable = observer(
|
|||||||
const option = useSchemaOptionsContext();
|
const option = useSchemaOptionsContext();
|
||||||
const components = {
|
const components = {
|
||||||
...option.components,
|
...option.components,
|
||||||
'Radio.Group': Select,
|
'Radio.Group': (props) => <Select data-testid="antd-select" {...props} />,
|
||||||
'Checkbox.Group': (props) => <Select multiple={true} mode="multiple" {...props} />,
|
'Checkbox.Group': (props) => <Select data-testid="antd-select" multiple={true} mode="multiple" {...props} />,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<CollectionProvider name={options.target}>
|
<CollectionProvider name={options.target}>
|
||||||
|
@ -79,6 +79,7 @@ export const AssociationFilterFilterBlockInitializer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-association-filter-filter-block"
|
||||||
className={css`
|
className={css`
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
`}
|
`}
|
||||||
|
@ -49,6 +49,7 @@ export const AssociationFilterInitializer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-association-filter"
|
||||||
className={css`
|
className={css`
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
`}
|
`}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { APIClientProvider, AssociationSelect, FormProvider, SchemaComponent } from '@nocobase/client';
|
import { APIClientProvider, AssociationSelect, FormProvider, SchemaComponent } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
mockRequest.onGet('/posts:list').reply(() => {
|
mockRequest.onGet('/posts:list').reply(() => {
|
||||||
|
@ -15,7 +15,7 @@ export const ViewSelect = observer(
|
|||||||
} = useContext(CalendarToolbarContext);
|
} = useContext(CalendarToolbarContext);
|
||||||
return (
|
return (
|
||||||
<div className="ant-btn-group">
|
<div className="ant-btn-group">
|
||||||
<Select popupMatchSelectWidth={false} value={view} onChange={onView}>
|
<Select data-testid="antd-select" popupMatchSelectWidth={false} value={view} onChange={onView}>
|
||||||
{views.map((name) => (
|
{views.map((name) => (
|
||||||
<Select.Option key={name} value={name}>
|
<Select.Option key={name} value={name}>
|
||||||
{messages[name]}
|
{messages[name]}
|
||||||
|
@ -45,6 +45,7 @@ export const CollectionSelect = connect(
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
placeholder={t('Select collection')}
|
placeholder={t('Select collection')}
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
{...others}
|
{...others}
|
||||||
|
@ -2,7 +2,7 @@ import { FormItem } from '@formily/antd-v5';
|
|||||||
import { CollectionManagerProvider, CollectionSelect, FormProvider, SchemaComponent } from '@nocobase/client';
|
import { CollectionManagerProvider, CollectionSelect, FormProvider, SchemaComponent } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { collections } from '../../../../test';
|
import { collections } from '../../../../testUtils';
|
||||||
|
|
||||||
const schema = {
|
const schema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -23,7 +23,7 @@ export const ColorSelect = connect(
|
|||||||
(props) => {
|
(props) => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
return (
|
return (
|
||||||
<Select {...props}>
|
<Select data-testid="antd-select" {...props}>
|
||||||
{Object.keys(colors).map((color) => (
|
{Object.keys(colors).map((color) => (
|
||||||
<Select.Option value={color}>
|
<Select.Option value={color}>
|
||||||
<Tag color={color}>{compile(colors[color] || colors.default)}</Tag>
|
<Tag color={color}>{compile(colors[color] || colors.default)}</Tag>
|
||||||
|
@ -64,6 +64,7 @@ const CronSetInternal = (props: CronSetProps) => {
|
|||||||
return (
|
return (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
allowClear
|
allowClear
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -50,13 +50,15 @@ export const DynamicComponent = (props) => {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
{component
|
<div data-testid="dynamic-component-filter-item">
|
||||||
? React.createElement(component, {
|
{component
|
||||||
value: props.value,
|
? React.createElement(component, {
|
||||||
onChange: props?.onChange,
|
value: props.value,
|
||||||
renderSchemaComponent,
|
onChange: props?.onChange,
|
||||||
})
|
renderSchemaComponent,
|
||||||
: renderSchemaComponent()}
|
})
|
||||||
|
: renderSchemaComponent()}
|
||||||
|
</div>
|
||||||
</FormContext.Provider>
|
</FormContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -42,7 +42,7 @@ export const FilterGroup = connect((props) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{remove && !mergedDisabled && (
|
{remove && !mergedDisabled && (
|
||||||
<a>
|
<a data-testid="close-icon-button">
|
||||||
<CloseCircleOutlined
|
<CloseCircleOutlined
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
@ -58,6 +58,7 @@ export const FilterGroup = connect((props) => {
|
|||||||
<Trans>
|
<Trans>
|
||||||
{'Meet '}
|
{'Meet '}
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
style={{ width: 'auto' }}
|
style={{ width: 'auto' }}
|
||||||
value={logic}
|
value={logic}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -5,8 +5,8 @@ import { Cascader, Select, Space } from 'antd';
|
|||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useCompile } from '../..';
|
import { useCompile } from '../..';
|
||||||
import { RemoveConditionContext } from './context';
|
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
|
import { RemoveConditionContext } from './context';
|
||||||
import { useValues } from './useValues';
|
import { useValues } from './useValues';
|
||||||
|
|
||||||
export const FilterItem = observer(
|
export const FilterItem = observer(
|
||||||
@ -20,6 +20,7 @@ export const FilterItem = observer(
|
|||||||
<div style={{ marginBottom: 8 }} className="nc-filter-item">
|
<div style={{ marginBottom: 8 }} className="nc-filter-item">
|
||||||
<Space>
|
<Space>
|
||||||
<Cascader
|
<Cascader
|
||||||
|
data-testid="antd-cascader"
|
||||||
className={css`
|
className={css`
|
||||||
width: 160px;
|
width: 160px;
|
||||||
`}
|
`}
|
||||||
@ -37,6 +38,7 @@ export const FilterItem = observer(
|
|||||||
placeholder={t('Select field')}
|
placeholder={t('Select field')}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
className={css`
|
className={css`
|
||||||
min-width: 110px;
|
min-width: 110px;
|
||||||
`}
|
`}
|
||||||
@ -50,7 +52,7 @@ export const FilterItem = observer(
|
|||||||
/>
|
/>
|
||||||
{!operator?.noValue ? <DynamicComponent value={value} schema={schema} onChange={setValue} /> : null}
|
{!operator?.noValue ? <DynamicComponent value={value} schema={schema} onChange={setValue} /> : null}
|
||||||
{!props.disabled && (
|
{!props.disabled && (
|
||||||
<a>
|
<a data-testid="close-icon-button">
|
||||||
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
@ -462,6 +462,7 @@ FormItem.Designer = function Designer() {
|
|||||||
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
{t('Popup size')}
|
{t('Popup size')}
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
bordered={false}
|
bordered={false}
|
||||||
options={[
|
options={[
|
||||||
{ label: t('Small'), value: 'small' },
|
{ label: t('Small'), value: 'small' },
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
mockRequest.onGet('/auth:check').reply(() => {
|
mockRequest.onGet('/auth:check').reply(() => {
|
||||||
|
@ -157,6 +157,7 @@ export const Templates = ({ style = {}, form }) => {
|
|||||||
<Space wrap>
|
<Space wrap>
|
||||||
<label style={labelStyle}>{t('Data template')}: </label>
|
<label style={labelStyle}>{t('Data template')}: </label>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
options={templateOptions}
|
options={templateOptions}
|
||||||
fieldNames={{ label: 'title', value: 'key' }}
|
fieldNames={{ label: 'title', value: 'key' }}
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFilterByTk } from '../../../../block-provider/BlockProvider';
|
import { useFilterByTk } from '../../../../block-provider/BlockProvider';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
import collections from './collections';
|
import collections from './collections';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFilterByTk } from '../../../../block-provider/BlockProvider';
|
import { useFilterByTk } from '../../../../block-provider/BlockProvider';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
import collections from './collections';
|
import collections from './collections';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
import collections from './collections';
|
import collections from './collections';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ const InternalGridCardBlockProvider = (props) => {
|
|||||||
|
|
||||||
export const GridCardBlockProvider = (props) => {
|
export const GridCardBlockProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<BlockProvider {...props}>
|
<BlockProvider data-testid="grid-card-block" {...props}>
|
||||||
<InternalGridCardBlockProvider {...props} />
|
<InternalGridCardBlockProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
);
|
);
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
mockRequest.onGet('/auth:check').reply(() => {
|
mockRequest.onGet('/auth:check').reply(() => {
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
SchemaComponentProvider,
|
SchemaComponentProvider,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
import collections from './collections';
|
import collections from './collections';
|
||||||
import data from './data';
|
import data from './data';
|
||||||
|
|
||||||
|
@ -33,16 +33,14 @@ const InternalListBlockProvider = (props) => {
|
|||||||
<FormContext.Provider value={form}>
|
<FormContext.Provider value={form}>
|
||||||
<FormLayout layout={'vertical'}>
|
<FormLayout layout={'vertical'}>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(css`
|
||||||
css`
|
.ant-description-input {
|
||||||
.ant-description-input {
|
line-height: 34px;
|
||||||
line-height: 34px;
|
}
|
||||||
}
|
.ant-formily-item-feedback-layout-loose {
|
||||||
.ant-formily-item-feedback-layout-loose {
|
display: inline;
|
||||||
display: inline;
|
}
|
||||||
}
|
`)}
|
||||||
`,
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +52,7 @@ const InternalListBlockProvider = (props) => {
|
|||||||
|
|
||||||
export const ListBlockProvider = (props) => {
|
export const ListBlockProvider = (props) => {
|
||||||
return (
|
return (
|
||||||
<BlockProvider {...props}>
|
<BlockProvider data-testid="list-block" {...props}>
|
||||||
<InternalListBlockProvider {...props} />
|
<InternalListBlockProvider {...props} />
|
||||||
</BlockProvider>
|
</BlockProvider>
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,10 @@ export const PageDesigner = ({ title }) => {
|
|||||||
<div className={'general-schema-designer'}>
|
<div className={'general-schema-designer'}>
|
||||||
<div className={'general-schema-designer-icons'}>
|
<div className={'general-schema-designer-icons'}>
|
||||||
<Space size={2} align={'center'}>
|
<Space size={2} align={'center'}>
|
||||||
<SchemaSettings title={<MenuOutlined style={{ cursor: 'pointer', fontSize: 12 }} />}>
|
<SchemaSettings
|
||||||
|
data-testid="page-designer-button"
|
||||||
|
title={<MenuOutlined style={{ cursor: 'pointer', fontSize: 12 }} />}
|
||||||
|
>
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
title={t('Enable page header')}
|
title={t('Enable page header')}
|
||||||
checked={!fieldSchema['x-component-props']?.disablePageHeader}
|
checked={!fieldSchema['x-component-props']?.disablePageHeader}
|
||||||
|
@ -175,6 +175,7 @@ export const InputRecordPicker: React.FC<any> = (props: IRecordPickerProps) => {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
{...others}
|
{...others}
|
||||||
mode={multiple ? 'multiple' : props.mode}
|
mode={multiple ? 'multiple' : props.mode}
|
||||||
fieldNames={fieldNames}
|
fieldNames={fieldNames}
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
TableV2,
|
TableV2,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mainCollections, mockAPIClient } from '../../../../test';
|
import { mainCollections, mockAPIClient } from '../../../../testUtils';
|
||||||
import data from './mockData';
|
import data from './mockData';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { APIClientProvider, FormProvider, RemoteSelect, SchemaComponent } from '@nocobase/client';
|
import { APIClientProvider, FormProvider, RemoteSelect, SchemaComponent } from '@nocobase/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
mockRequest.onGet('/posts:list').reply(() => {
|
mockRequest.onGet('/posts:list').reply(() => {
|
||||||
|
@ -43,6 +43,7 @@ const ObjectSelect = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AntdSelect
|
<AntdSelect
|
||||||
|
data-testid="antd-select"
|
||||||
value={toValue(value)}
|
value={toValue(value)}
|
||||||
defaultValue={toValue(defaultValue)}
|
defaultValue={toValue(defaultValue)}
|
||||||
allowClear
|
allowClear
|
||||||
@ -108,6 +109,7 @@ const InternalSelect = connect(
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<AntdSelect
|
<AntdSelect
|
||||||
|
data-testid="antd-select"
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={filterOption}
|
filterOption={filterOption}
|
||||||
allowClear
|
allowClear
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
import { range } from 'lodash';
|
import { range } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
import collections from './collections';
|
import collections from './collections';
|
||||||
|
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { mockAPIClient } from '../../../../test';
|
import { mockAPIClient } from '../../../../testUtils';
|
||||||
|
|
||||||
const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value));
|
const sleep = (value: number) => new Promise((resolve) => setTimeout(resolve, value));
|
||||||
const { apiClient, mockRequest } = mockAPIClient();
|
const { apiClient, mockRequest } = mockAPIClient();
|
||||||
|
@ -77,6 +77,7 @@ const ConstantTypes = {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
placeholder={t('Select')}
|
placeholder={t('Select')}
|
||||||
|
@ -93,12 +93,7 @@ export const SortableItem: React.FC<SortableItemProps> = observer(
|
|||||||
|
|
||||||
export const DragHandler = (props) => {
|
export const DragHandler = (props) => {
|
||||||
const { draggable } = useContext(SortableContext);
|
const { draggable } = useContext(SortableContext);
|
||||||
const { isDragging, attributes, listeners, setNodeRef, transform } = draggable;
|
const { attributes, listeners, setNodeRef } = draggable;
|
||||||
const style = transform
|
|
||||||
? {
|
|
||||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -118,8 +113,6 @@ export const DragHandler = (props) => {
|
|||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
// backgroundColor: '#333',
|
// backgroundColor: '#333',
|
||||||
lineHeight: 0,
|
lineHeight: 0,
|
||||||
height: 2,
|
|
||||||
width: 2,
|
|
||||||
fontSize: 0,
|
fontSize: 0,
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
}}
|
}}
|
||||||
|
@ -5,6 +5,7 @@ import classNames from 'classnames';
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useCollection } from '../collection-manager';
|
||||||
import { useCollectMenuItem, useMenuItem } from '../hooks/useMenuItem';
|
import { useCollectMenuItem, useMenuItem } from '../hooks/useMenuItem';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
import { SchemaComponent, useActionContext } from '../schema-component';
|
import { SchemaComponent, useActionContext } from '../schema-component';
|
||||||
@ -251,6 +252,7 @@ SchemaInitializer.Button = observer(
|
|||||||
const { Component: CollectComponent, getMenuItem, clean } = useMenuItem();
|
const { Component: CollectComponent, getMenuItem, clean } = useMenuItem();
|
||||||
const menuItems = useRef([]);
|
const menuItems = useRef([]);
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
|
const { name } = useCollection();
|
||||||
|
|
||||||
const changeMenu = (v: boolean) => {
|
const changeMenu = (v: boolean) => {
|
||||||
setVisible(v);
|
setVisible(v);
|
||||||
@ -266,6 +268,10 @@ SchemaInitializer.Button = observer(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (others['data-testid'] && name) {
|
||||||
|
others['data-testid'] = `${others['data-testid']}-${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
const buttonDom = component || (
|
const buttonDom = component || (
|
||||||
<Button
|
<Button
|
||||||
type={'dashed'}
|
type={'dashed'}
|
||||||
@ -514,7 +520,7 @@ SchemaInitializer.ActionModal = function ActionModal(props: SchemaInitializerAct
|
|||||||
async run() {
|
async run() {
|
||||||
await onCancel?.();
|
await onCancel?.();
|
||||||
ctx.setVisible(false);
|
ctx.setVisible(false);
|
||||||
form.reset();
|
void form.reset();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [onCancel]);
|
}, [onCancel]);
|
||||||
@ -529,7 +535,7 @@ SchemaInitializer.ActionModal = function ActionModal(props: SchemaInitializerAct
|
|||||||
await form.validate();
|
await form.validate();
|
||||||
await onSubmit?.(form.values);
|
await onSubmit?.(form.values);
|
||||||
ctx.setVisible(false);
|
ctx.setVisible(false);
|
||||||
form.reset();
|
void form.reset();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [onSubmit]);
|
}, [onSubmit]);
|
||||||
|
@ -2,6 +2,7 @@ import { gridRowColWrap } from '../utils';
|
|||||||
|
|
||||||
// 页面里添加区块
|
// 页面里添加区块
|
||||||
export const BlockInitializers = {
|
export const BlockInitializers = {
|
||||||
|
'data-testid': 'add-block-button-in-page',
|
||||||
title: '{{t("Add block")}}',
|
title: '{{t("Add block")}}',
|
||||||
icon: 'PlusOutlined',
|
icon: 'PlusOutlined',
|
||||||
wrap: gridRowColWrap,
|
wrap: gridRowColWrap,
|
||||||
|
@ -14,6 +14,7 @@ export const BulkEditFormItemInitializers = (props: any) => {
|
|||||||
const associationFields = useAssociatedFormItemInitializerFields({ readPretty: true, block: 'Form' });
|
const associationFields = useAssociatedFormItemInitializerFields({ readPretty: true, block: 'Form' });
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-bulk-edit-form-item"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
items={union<any>(
|
items={union<any>(
|
||||||
|
@ -2,6 +2,7 @@ import { useCollection } from '../../';
|
|||||||
|
|
||||||
// 日历的操作配置
|
// 日历的操作配置
|
||||||
export const CalendarActionInitializers = {
|
export const CalendarActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-calendar-block',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: { marginLeft: 8 },
|
style: { marginLeft: 8 },
|
||||||
|
@ -2,6 +2,7 @@ import { useCollection } from '../..';
|
|||||||
|
|
||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const CalendarFormActionInitializers = {
|
export const CalendarFormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-calendar-form',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
|
@ -34,6 +34,7 @@ export const CustomFormItemInitializers = (props: any) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-custom-form-item"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
items={fieldItems}
|
items={fieldItems}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const DetailsActionInitializers = {
|
export const DetailsActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-details-block',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const FilterFormActionInitializers = {
|
export const FilterFormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-filter-form',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
|
@ -26,6 +26,7 @@ const FormTriggerWorkflowActionInitializer = {
|
|||||||
|
|
||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const FormActionInitializers = {
|
export const FormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-form-block',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
@ -159,6 +160,7 @@ export const FormActionInitializers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CreateFormActionInitializers = {
|
export const CreateFormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-create-form',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
@ -292,6 +294,7 @@ export const CreateFormActionInitializers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const UpdateFormActionInitializers = {
|
export const UpdateFormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-update-form',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
@ -424,6 +427,7 @@ export const UpdateFormActionInitializers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const BulkEditFormActionInitializers = {
|
export const BulkEditFormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-bulk-edit-form',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
|
@ -78,6 +78,7 @@ export const FormItemInitializers = (props: any) => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-form-item"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
items={fieldItems}
|
items={fieldItems}
|
||||||
@ -151,6 +152,7 @@ export const FilterFormItemInitializers = (props: any) => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-filter-form-item"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
items={fieldItems}
|
items={fieldItems}
|
||||||
|
@ -2,6 +2,7 @@ import { useCollection } from '../../collection-manager';
|
|||||||
|
|
||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const GridCardActionInitializers = {
|
export const GridCardActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-grid-card-block',
|
||||||
title: "{{t('Configure actions')}}",
|
title: "{{t('Configure actions')}}",
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
@ -139,6 +140,7 @@ export const GridCardActionInitializers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GridCardItemActionInitializers = {
|
export const GridCardItemActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-grid-card-item',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useCollection } from '../../';
|
import { useCollection } from '../../';
|
||||||
|
|
||||||
export const KanbanActionInitializers = {
|
export const KanbanActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-kanban-block',
|
||||||
title: "{{t('Configure actions')}}",
|
title: "{{t('Configure actions')}}",
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
|
@ -2,6 +2,7 @@ import { useCollection } from '../../collection-manager';
|
|||||||
|
|
||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const ListActionInitializers = {
|
export const ListActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-list-block',
|
||||||
title: "{{t('Configure actions')}}",
|
title: "{{t('Configure actions')}}",
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
@ -143,6 +144,7 @@ export const ListActionInitializers = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ListItemActionInitializers = {
|
export const ListItemActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-list-item',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
items: [
|
items: [
|
||||||
|
@ -6,6 +6,7 @@ const useVisibleCollection = () => {
|
|||||||
};
|
};
|
||||||
// 表单的操作配置
|
// 表单的操作配置
|
||||||
export const ReadPrettyFormActionInitializers = {
|
export const ReadPrettyFormActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-read-pretty-form',
|
||||||
title: '{{t("Configure actions")}}',
|
title: '{{t("Configure actions")}}',
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { union } from 'lodash';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCompile } from '../../schema-component';
|
||||||
import { SchemaInitializer } from '../SchemaInitializer';
|
import { SchemaInitializer } from '../SchemaInitializer';
|
||||||
import {
|
import {
|
||||||
gridRowColWrap,
|
gridRowColWrap,
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
useFormItemInitializerFields,
|
useFormItemInitializerFields,
|
||||||
useInheritsFormItemInitializerFields,
|
useInheritsFormItemInitializerFields,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { useCompile } from '../../schema-component';
|
|
||||||
|
|
||||||
export const ReadPrettyFormItemInitializers = (props: any) => {
|
export const ReadPrettyFormItemInitializers = (props: any) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -72,6 +71,7 @@ export const ReadPrettyFormItemInitializers = (props: any) => {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="configure-fields-button-of-read-pretty-form-item"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
items={fieldItems}
|
items={fieldItems}
|
||||||
|
@ -7,6 +7,7 @@ export const RecordFormBlockInitializers = (props: any) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="add-block-button-in-record-form-block"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
title={t('Add block')}
|
title={t('Add block')}
|
||||||
icon={'PlusOutlined'}
|
icon={'PlusOutlined'}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// 表格操作配置
|
// 表格操作配置
|
||||||
export const SubTableActionInitializers = {
|
export const SubTableActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-sub-table',
|
||||||
title: "{{t('Configure actions')}}",
|
title: "{{t('Configure actions')}}",
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
|
@ -3,6 +3,7 @@ import { useCollection } from '../../';
|
|||||||
|
|
||||||
// 表格操作配置
|
// 表格操作配置
|
||||||
export const TableActionInitializers = {
|
export const TableActionInitializers = {
|
||||||
|
'data-testid': 'configure-actions-button-of-table-block',
|
||||||
title: "{{t('Configure actions')}}",
|
title: "{{t('Configure actions')}}",
|
||||||
icon: 'SettingOutlined',
|
icon: 'SettingOutlined',
|
||||||
style: {
|
style: {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCollection } from '../../collection-manager';
|
||||||
import { useCompile } from '../../schema-component';
|
import { useCompile } from '../../schema-component';
|
||||||
import { SchemaInitializer } from '../SchemaInitializer';
|
import { SchemaInitializer } from '../SchemaInitializer';
|
||||||
import {
|
import {
|
||||||
@ -12,7 +13,8 @@ import {
|
|||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
export const TableColumnInitializers = (props: any) => {
|
export const TableColumnInitializers = (props: any) => {
|
||||||
const { items = [], action = true } = props;
|
const { action = true } = props;
|
||||||
|
const { name } = useCollection();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
@ -69,6 +71,7 @@ export const TableColumnInitializers = (props: any) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid={`configure-columns-button-of-table-block`}
|
||||||
insertPosition={'beforeEnd'}
|
insertPosition={'beforeEnd'}
|
||||||
icon={'SettingOutlined'}
|
icon={'SettingOutlined'}
|
||||||
wrap={(s) => {
|
wrap={(s) => {
|
||||||
|
@ -9,6 +9,7 @@ export const TableSelectorInitializers = (props: any) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Button
|
<SchemaInitializer.Button
|
||||||
|
data-testid="add-block-button-in-table-selector"
|
||||||
wrap={gridRowColWrap}
|
wrap={gridRowColWrap}
|
||||||
title={component ? null : t('Add block')}
|
title={component ? null : t('Add block')}
|
||||||
icon={'PlusOutlined'}
|
icon={'PlusOutlined'}
|
||||||
|
@ -127,7 +127,7 @@ export const BulkEditField = (props: any) => {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Select defaultValue={type} value={type} onChange={typeChangeHandler}>
|
<Select data-testid="antd-select" defaultValue={type} value={type} onChange={typeChangeHandler}>
|
||||||
<Select.Option value={BulkEditFormItemValueType.RemainsTheSame}>{t('Remains the same')}</Select.Option>
|
<Select.Option value={BulkEditFormItemValueType.RemainsTheSame}>{t('Remains the same')}</Select.Option>
|
||||||
<Select.Option value={BulkEditFormItemValueType.ChangedTo}>{t('Changed to')}</Select.Option>
|
<Select.Option value={BulkEditFormItemValueType.ChangedTo}>{t('Changed to')}</Select.Option>
|
||||||
<Select.Option value={BulkEditFormItemValueType.Clear}>{t('Clear')}</Select.Option>
|
<Select.Option value={BulkEditFormItemValueType.Clear}>{t('Clear')}</Select.Option>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import { SchemaSettings } from '../schema-settings';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDesignable } from '../schema-component';
|
import { useDesignable } from '../schema-component';
|
||||||
|
import { SchemaSettings } from '../schema-settings';
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
openMode?: boolean;
|
openMode?: boolean;
|
||||||
@ -49,6 +49,7 @@ export const OpenModeSchemaItems: React.FC<Options> = (options) => {
|
|||||||
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
{t('Popup size')}
|
{t('Popup size')}
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
bordered={false}
|
bordered={false}
|
||||||
options={[
|
options={[
|
||||||
{ label: t('Small'), value: 'small' },
|
{ label: t('Small'), value: 'small' },
|
||||||
|
@ -171,6 +171,7 @@ function SelectItem(props) {
|
|||||||
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
{title}
|
{title}
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
value={value}
|
value={value}
|
||||||
|
@ -3,7 +3,8 @@ import { css } from '@emotion/css';
|
|||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useMemo } from 'react';
|
import _ from 'lodash';
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DragHandler, useCompile, useDesignable, useGridContext, useGridRowContext } from '../schema-component';
|
import { DragHandler, useCompile, useDesignable, useGridContext, useGridRowContext } from '../schema-component';
|
||||||
import { gridRowColWrap } from '../schema-initializer/utils';
|
import { gridRowColWrap } from '../schema-initializer/utils';
|
||||||
@ -35,7 +36,72 @@ const overrideAntdCSS = css`
|
|||||||
& .ant-space-item .anticon {
|
& .ant-space-item .anticon {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const DesignerControl = ({ onShow, children }) => {
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
ref={divRef}
|
||||||
|
className={classNames('general-schema-designer', overrideAntdCSS)}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const GeneralSchemaDesigner = (props: any) => {
|
export const GeneralSchemaDesigner = (props: any) => {
|
||||||
const { disableInitializer, title, template, draggable = true } = props;
|
const { disableInitializer, title, template, draggable = true } = props;
|
||||||
const { dn, designable } = useDesignable();
|
const { dn, designable } = useDesignable();
|
||||||
@ -43,12 +109,15 @@ export const GeneralSchemaDesigner = (props: any) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
const schemaSettingsProps = {
|
const schemaSettingsProps = {
|
||||||
dn,
|
dn,
|
||||||
field,
|
field,
|
||||||
fieldSchema,
|
fieldSchema,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onShow = useCallback((value) => setVisible(value), []);
|
||||||
|
|
||||||
const rowCtx = useGridRowContext();
|
const rowCtx = useGridRowContext();
|
||||||
const ctx = useGridContext();
|
const ctx = useGridContext();
|
||||||
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
const templateName = ['FormItem', 'ReadPrettyFormItem'].includes(template?.componentName)
|
||||||
@ -58,9 +127,7 @@ export const GeneralSchemaDesigner = (props: any) => {
|
|||||||
return {
|
return {
|
||||||
insertPosition: 'afterEnd',
|
insertPosition: 'afterEnd',
|
||||||
wrap: rowCtx?.cols?.length > 1 ? undefined : gridRowColWrap,
|
wrap: rowCtx?.cols?.length > 1 ? undefined : gridRowColWrap,
|
||||||
component: (
|
component: <PlusOutlined data-testid="designer-add-block" style={{ cursor: 'pointer', fontSize: 14 }} />,
|
||||||
<PlusOutlined data-testid="GeneralSchemaDesigner-Initializer" style={{ cursor: 'pointer', fontSize: 14 }} />
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}, [rowCtx?.cols?.length]);
|
}, [rowCtx?.cols?.length]);
|
||||||
|
|
||||||
@ -69,45 +136,46 @@ export const GeneralSchemaDesigner = (props: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('general-schema-designer', overrideAntdCSS)}>
|
<DesignerControl onShow={onShow}>
|
||||||
{title && (
|
{visible ? (
|
||||||
<div className={classNames('general-schema-designer-title', titleCss)}>
|
<>
|
||||||
<Space size={2}>
|
{title && (
|
||||||
<span className={'title-tag'}>{compile(title)}</span>
|
<div className={classNames('general-schema-designer-title', titleCss)}>
|
||||||
{template && (
|
<Space size={2}>
|
||||||
<span className={'title-tag'}>
|
<span className={'title-tag'}>{compile(title)}</span>
|
||||||
{t('Reference template')}: {templateName || t('Untitled')}
|
{template && (
|
||||||
</span>
|
<span className={'title-tag'}>
|
||||||
)}
|
{t('Reference template')}: {templateName || t('Untitled')}
|
||||||
</Space>
|
</span>
|
||||||
</div>
|
)}
|
||||||
)}
|
</Space>
|
||||||
<div className={'general-schema-designer-icons'}>
|
</div>
|
||||||
<Space size={2} align={'center'}>
|
|
||||||
{draggable && (
|
|
||||||
<DragHandler>
|
|
||||||
<DragOutlined data-testid="GeneralSchemaDesigner-DragHandler" />
|
|
||||||
</DragHandler>
|
|
||||||
)}
|
)}
|
||||||
{!disableInitializer &&
|
<div className={'general-schema-designer-icons'}>
|
||||||
(ctx?.InitializerComponent ? (
|
<Space size={2} align={'center'}>
|
||||||
<ctx.InitializerComponent {...initializerProps} />
|
{draggable && (
|
||||||
) : (
|
<DragHandler>
|
||||||
ctx?.renderSchemaInitializer?.(initializerProps)
|
<DragOutlined data-testid="designer-drag" />
|
||||||
))}
|
</DragHandler>
|
||||||
<SchemaSettings
|
)}
|
||||||
title={
|
{!disableInitializer &&
|
||||||
<MenuOutlined
|
(ctx?.InitializerComponent ? (
|
||||||
data-testid="GeneralSchemaDesigner-SchemaSettings"
|
<ctx.InitializerComponent {...initializerProps} />
|
||||||
style={{ cursor: 'pointer', fontSize: 12 }}
|
) : (
|
||||||
/>
|
ctx?.renderSchemaInitializer?.(initializerProps)
|
||||||
}
|
))}
|
||||||
{...schemaSettingsProps}
|
<SchemaSettings
|
||||||
>
|
title={
|
||||||
{props.children}
|
<MenuOutlined data-testid="designer-schema-settings" style={{ cursor: 'pointer', fontSize: 12 }} />
|
||||||
</SchemaSettings>
|
}
|
||||||
</Space>
|
{...schemaSettingsProps}
|
||||||
</div>
|
>
|
||||||
</div>
|
{props.children}
|
||||||
|
</SchemaSettings>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</DesignerControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -72,6 +72,7 @@ export const FormFieldLinkageRuleAction = observer(
|
|||||||
placeholder={t('Select field')}
|
placeholder={t('Select field')}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
value={operator}
|
value={operator}
|
||||||
className={css`
|
className={css`
|
||||||
@ -92,7 +93,7 @@ export const FormFieldLinkageRuleAction = observer(
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!props.disabled && (
|
{!props.disabled && (
|
||||||
<a>
|
<a data-testid="close-icon-button">
|
||||||
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
@ -141,7 +142,7 @@ export const FormButtonLinkageRuleAction = observer(
|
|||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
{!props.disabled && (
|
{!props.disabled && (
|
||||||
<a>
|
<a data-testid="close-icon-button">
|
||||||
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Input, Select } from 'antd';
|
import { Input, Select } from 'antd';
|
||||||
import { css } from '@emotion/css';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DynamicComponent } from './DynamicComponent';
|
|
||||||
import { Variable } from '.././../schema-component';
|
import { Variable } from '.././../schema-component';
|
||||||
|
import { DynamicComponent } from './DynamicComponent';
|
||||||
import { useVariableOptions } from './Variables';
|
import { useVariableOptions } from './Variables';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
@ -16,6 +15,7 @@ export const ValueDynamicComponent = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Input.Group compact>
|
<Input.Group compact>
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
value={mode}
|
value={mode}
|
||||||
style={{ width: 150 }}
|
style={{ width: 150 }}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -175,7 +175,7 @@ export const SchemaSettings: React.FC<SchemaSettingsProps> & SchemaSettingsNeste
|
|||||||
`}
|
`}
|
||||||
menu={{ items }}
|
menu={{ items }}
|
||||||
>
|
>
|
||||||
{typeof title === 'string' ? <span>{title}</span> : title}
|
<div data-testid={props['data-testid']}>{typeof title === 'string' ? <span>{title}</span> : title}</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -708,6 +708,7 @@ SchemaSettings.SelectItem = function SelectItem(props) {
|
|||||||
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ alignItems: 'center', display: 'flex', justifyContent: 'space-between' }}>
|
||||||
{title}
|
{title}
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
|
@ -18,8 +18,5 @@ export const SystemSettingsProvider: React.FC<{ children?: ReactNode }> = (props
|
|||||||
return render();
|
return render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主要是为了方便在 e2e 测试中获取到 adminSchemaUid
|
|
||||||
localStorage.setItem('NOCOBASE_SYSTEM_SETTINGS', JSON.stringify(result.data));
|
|
||||||
|
|
||||||
return <SystemSettingsContext.Provider value={result}>{props.children}</SystemSettingsContext.Provider>;
|
return <SystemSettingsContext.Provider value={result}>{props.children}</SystemSettingsContext.Provider>;
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export { default as collections } from './collections';
|
export { default as collections } from './collections';
|
||||||
export { mainCollections } from './mainCollections';
|
export { mainCollections } from './mainCollections';
|
||||||
export * from './mockAPIClient';
|
export * from './mockAPIClient';
|
||||||
|
|
@ -172,6 +172,7 @@ export const CurrentUser = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
data-testid="user-center-button"
|
||||||
className={css`
|
className={css`
|
||||||
max-width: 160px;
|
max-width: 160px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -27,6 +27,7 @@ export const useLanguageSettings = () => {
|
|||||||
>
|
>
|
||||||
{t('Language')}{' '}
|
{t('Language')}{' '}
|
||||||
<Select
|
<Select
|
||||||
|
data-testid="antd-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
style={{ minWidth: 100 }}
|
style={{ minWidth: 100 }}
|
||||||
bordered={false}
|
bordered={false}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user