chore: upgrade antd to v5.12.2 (#3185)
* chore: upgrade antd to v5.12.2 * refactor: rename to StablePopover * fix: fix tests * test: fix e2e * chore(CI): optimize E2E workflow * chore: fix playwright * fix: re-generate yarn.lock to fix e2e errors * test: update locator * fix: fix build errors * fix: fix unit tests * fix: fix e2e tests * fix: should not auto close when clicking menu item * fix: fix column width * test: increase e2e timeout-minutes to 90 * test: fix e2e * fix: update dockerfile * fix: npmignore * fix: app component false --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
c2d95a1620
commit
6bd0568d08
10
.github/workflows/nocobase-test-e2e.yml
vendored
10
.github/workflows/nocobase-test-e2e.yml
vendored
@ -43,6 +43,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'yarn'
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@ -50,7 +51,9 @@ jobs:
|
||||
- uses: actions/cache@v3
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
@ -60,7 +63,7 @@ jobs:
|
||||
run: yarn build
|
||||
env:
|
||||
__E2E__: true
|
||||
- run: npx playwright install --with-deps
|
||||
- run: npx playwright install chromium --with-deps
|
||||
- name: Test with postgres
|
||||
run: yarn e2e test -x --skip-reporter
|
||||
env:
|
||||
@ -73,7 +76,4 @@ jobs:
|
||||
DB_USER: nocobase
|
||||
DB_PASSWORD: password
|
||||
DB_DATABASE: nocobase
|
||||
DB_UNDERSCORED: ${{ matrix.underscored }}
|
||||
DB_SCHEMA: ${{ matrix.schema }}
|
||||
COLLECTION_MANAGER_SCHEMA: ${{ matrix.collection_schema }}
|
||||
timeout-minutes: 90
|
||||
|
@ -18,11 +18,11 @@ RUN cd /tmp && \
|
||||
RUN yarn install && yarn build --no-dts
|
||||
|
||||
RUN git checkout -b release-$(date +'%Y%m%d%H%M%S') \
|
||||
&& yarn version:alpha -y \
|
||||
&& git config user.email "test@mail.com" \
|
||||
&& yarn version:alpha -y
|
||||
RUN git config user.email "test@mail.com" \
|
||||
&& git config user.name "test" && git add . \
|
||||
&& git commit -m "chore(versions): test publish packages xxx" \
|
||||
&& yarn release:force --registry $VERDACCIO_URL
|
||||
&& git commit -m "chore(versions): test publish packages"
|
||||
RUN yarn release:force --registry $VERDACCIO_URL
|
||||
|
||||
RUN yarn config set registry $VERDACCIO_URL
|
||||
WORKDIR /app
|
||||
|
@ -51,8 +51,7 @@
|
||||
"react-router": "^6.11.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"antd": "5.8.6",
|
||||
"@formily/antd-v5": "1.1.7"
|
||||
"nwsapi": "2.2.7"
|
||||
},
|
||||
"config": {
|
||||
"ghooks": {
|
||||
@ -73,7 +72,7 @@
|
||||
"@commitlint/config-conventional": "^16.0.0",
|
||||
"@commitlint/prompt-cli": "^16.1.0",
|
||||
"@faker-js/faker": "8.1.0",
|
||||
"@playwright/test": "^1.37.1",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@swc/core": "^1.3.92",
|
||||
"@swc/jest": "^0.2.29",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
|
@ -15,7 +15,7 @@
|
||||
"@dnd-kit/modifiers": "^6.0.0",
|
||||
"@dnd-kit/sortable": "^6.0.0",
|
||||
"@emotion/css": "^11.7.1",
|
||||
"@formily/antd-v5": "^1.1.7",
|
||||
"@formily/antd-v5": "^1.1.9",
|
||||
"@formily/core": "^2.2.27",
|
||||
"@formily/grid": "^2.2.27",
|
||||
"@formily/json-schema": "^2.2.27",
|
||||
@ -28,9 +28,8 @@
|
||||
"@nocobase/evaluators": "0.17.0-alpha.7",
|
||||
"@nocobase/sdk": "0.17.0-alpha.7",
|
||||
"@nocobase/utils": "0.17.0-alpha.7",
|
||||
"@types/requirejs": "^2.1.34",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "^5.8.6",
|
||||
"antd": "^5.12.2",
|
||||
"antd-style": "3.4.5",
|
||||
"axios": "^0.26.1",
|
||||
"classnames": "^2.3.1",
|
||||
|
@ -1,15 +1,7 @@
|
||||
// @ts-nocheck
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
/** vim: et:ts=4:sw=4:sts=4
|
||||
* @license RequireJS 2.3.6 Copyright jQuery Foundation and other contributors.
|
||||
* Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE
|
||||
*/
|
||||
//Not using strict: uneven strict support in browsers, #392, and causes
|
||||
//problems with requirejs.exec()/transpiler plugins that may not be strict.
|
||||
/*jslint regexp: true, nomen: true, sloppy: true */
|
||||
/*global window, navigator, document, importScripts, setTimeout, opera */
|
||||
/// <reference types="@types/requirejs" />
|
||||
import type { Require, RequireDefine } from './types'
|
||||
|
||||
export interface RequireJS {
|
||||
require: Require
|
||||
|
365
packages/core/client/src/application/utils/types.ts
Normal file
365
packages/core/client/src/application/utils/types.ts
Normal file
@ -0,0 +1,365 @@
|
||||
/** vim: et:ts=4:sw=4:sts=4
|
||||
* @license RequireJS 2.3.6 Copyright jQuery Foundation and other contributors.
|
||||
* Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE
|
||||
*/
|
||||
//Not using strict: uneven strict support in browsers, #392, and causes
|
||||
//problems with requirejs.exec()/transpiler plugins that may not be strict.
|
||||
/*jslint regexp: true, nomen: true, sloppy: true */
|
||||
/*global window, navigator, document, importScripts, setTimeout, opera */
|
||||
|
||||
interface RequireModule {
|
||||
/** */
|
||||
config(): {};
|
||||
}
|
||||
|
||||
interface RequireMap {
|
||||
/** */
|
||||
prefix: string;
|
||||
|
||||
/** */
|
||||
name: string;
|
||||
|
||||
/** */
|
||||
parentMap: RequireMap;
|
||||
|
||||
/** */
|
||||
url: string;
|
||||
|
||||
/** */
|
||||
originalName: string;
|
||||
|
||||
/** */
|
||||
fullName: string;
|
||||
}
|
||||
|
||||
interface RequireError extends Error {
|
||||
/**
|
||||
* The error ID that maps to an ID on a web page.
|
||||
*/
|
||||
requireType: string;
|
||||
|
||||
/**
|
||||
* Required modules.
|
||||
*/
|
||||
requireModules: string[] | null;
|
||||
|
||||
/**
|
||||
* The original error, if there is one (might be null).
|
||||
*/
|
||||
originalError: Error;
|
||||
}
|
||||
|
||||
interface RequireShim {
|
||||
/**
|
||||
* List of dependencies.
|
||||
*/
|
||||
deps?: string[] | undefined;
|
||||
|
||||
/**
|
||||
* Name the module will be exported as.
|
||||
*/
|
||||
exports?: string | undefined;
|
||||
|
||||
/**
|
||||
* Initialize function with all dependcies passed in,
|
||||
* if the function returns a value then that value is used
|
||||
* as the module export value instead of the object
|
||||
* found via the 'exports' string.
|
||||
* @param dependencies
|
||||
* @return
|
||||
*/
|
||||
init?: ((...dependencies: any[]) => any) | undefined;
|
||||
}
|
||||
|
||||
interface RequireConfig {
|
||||
/**
|
||||
* The root path to use for all module lookups.
|
||||
*/
|
||||
baseUrl?: string | undefined;
|
||||
|
||||
/**
|
||||
* Path mappings for module names not found directly under
|
||||
* baseUrl.
|
||||
*/
|
||||
paths?: { [key: string]: any } | undefined;
|
||||
|
||||
/**
|
||||
* Dictionary of Shim's.
|
||||
* Can be of type RequireShim or string[] of dependencies
|
||||
*/
|
||||
shim?: { [key: string]: RequireShim | string[] } | undefined;
|
||||
|
||||
/**
|
||||
* For the given module prefix, instead of loading the
|
||||
* module with the given ID, substitude a different
|
||||
* module ID.
|
||||
*
|
||||
* @example
|
||||
* requirejs.config({
|
||||
* map: {
|
||||
* 'some/newmodule': {
|
||||
* 'foo': 'foo1.2'
|
||||
* },
|
||||
* 'some/oldmodule': {
|
||||
* 'foo': 'foo1.0'
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
map?:
|
||||
| {
|
||||
[id: string]: {
|
||||
[id: string]: string;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
|
||||
/**
|
||||
* Allows pointing multiple module IDs to a module ID that contains a bundle of modules.
|
||||
*
|
||||
* @example
|
||||
* requirejs.config({
|
||||
* bundles: {
|
||||
* 'primary': ['main', 'util', 'text', 'text!template.html'],
|
||||
* 'secondary': ['text!secondary.html']
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
bundles?: { [key: string]: string[] } | undefined;
|
||||
|
||||
/**
|
||||
* AMD configurations, use module.config() to access in
|
||||
* define() functions
|
||||
*/
|
||||
config?: { [id: string]: {} } | undefined;
|
||||
|
||||
/**
|
||||
* Configures loading modules from CommonJS packages.
|
||||
*/
|
||||
packages?: {} | undefined;
|
||||
|
||||
/**
|
||||
* The number of seconds to wait before giving up on loading
|
||||
* a script. The default is 7 seconds.
|
||||
*/
|
||||
waitSeconds?: number | undefined;
|
||||
|
||||
/**
|
||||
* A name to give to a loading context. This allows require.js
|
||||
* to load multiple versions of modules in a page, as long as
|
||||
* each top-level require call specifies a unique context string.
|
||||
*/
|
||||
context?: string | undefined;
|
||||
|
||||
/**
|
||||
* An array of dependencies to load.
|
||||
*/
|
||||
deps?: string[] | undefined;
|
||||
|
||||
/**
|
||||
* A function to pass to require that should be require after
|
||||
* deps have been loaded.
|
||||
* @param modules
|
||||
*/
|
||||
callback?: ((...modules: any[]) => void) | undefined;
|
||||
|
||||
/**
|
||||
* If set to true, an error will be thrown if a script loads
|
||||
* that does not call define() or have shim exports string
|
||||
* value that can be checked.
|
||||
*/
|
||||
enforceDefine?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* If set to true, document.createElementNS() will be used
|
||||
* to create script elements.
|
||||
*/
|
||||
xhtml?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Extra query string arguments appended to URLs that RequireJS
|
||||
* uses to fetch resources. Most useful to cache bust when
|
||||
* the browser or server is not configured correctly.
|
||||
*
|
||||
* As of RequireJS 2.2.0, urlArgs can be a function. If a
|
||||
* function, it will receive the module ID and the URL as
|
||||
* parameters, and it should return a string that will be added
|
||||
* to the end of the URL. Return an empty string if no args.
|
||||
* Be sure to take care of adding the '?' or '&' depending on
|
||||
* the existing state of the URL.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* urlArgs: "bust=" + (new Date()).getTime()
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* requirejs.config({
|
||||
* urlArgs: function(id, url) {
|
||||
* var args = 'v=1';
|
||||
* if (url.indexOf('view.html') !== -1) {
|
||||
* args = 'v=2'
|
||||
* }
|
||||
*
|
||||
* return (url.indexOf('?') === -1 ? '?' : '&') + args;
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
urlArgs?: string | ((id: string, url: string) => string) | undefined;
|
||||
|
||||
/**
|
||||
* Specify the value for the type="" attribute used for script
|
||||
* tags inserted into the document by RequireJS. Default is
|
||||
* "text/javascript". To use Firefox's JavasScript 1.8
|
||||
* features, use "text/javascript;version=1.8".
|
||||
*/
|
||||
scriptType?: string | undefined;
|
||||
|
||||
/**
|
||||
* If set to true, skips the data-main attribute scanning done
|
||||
* to start module loading. Useful if RequireJS is embedded in
|
||||
* a utility library that may interact with other RequireJS
|
||||
* library on the page, and the embedded version should not do
|
||||
* data-main loading.
|
||||
*/
|
||||
skipDataMain?: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Allow extending requirejs to support Subresource Integrity
|
||||
* (SRI).
|
||||
*/
|
||||
onNodeCreated?:
|
||||
| ((node: HTMLScriptElement, config: RequireConfig, moduleName: string, url: string) => void)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export interface Require {
|
||||
/**
|
||||
* Configure require.js
|
||||
*/
|
||||
config(config: RequireConfig): Require;
|
||||
|
||||
/**
|
||||
* CommonJS require call
|
||||
* @param module Module to load
|
||||
* @return The loaded module
|
||||
*/
|
||||
(module: string): any;
|
||||
|
||||
/**
|
||||
* Start the main app logic.
|
||||
* Callback is optional.
|
||||
* Can alternatively use deps and callback.
|
||||
* @param modules Required modules to load.
|
||||
*/
|
||||
(modules: string[]): void;
|
||||
|
||||
/**
|
||||
* @see Require()
|
||||
* @param ready Called when required modules are ready.
|
||||
*/
|
||||
(modules: string[], ready: Function): void;
|
||||
|
||||
/**
|
||||
* @see http://requirejs.org/docs/api.html#errbacks
|
||||
* @param ready Called when required modules are ready.
|
||||
*/
|
||||
(modules: string[], ready: Function, errback: Function): void;
|
||||
|
||||
/**
|
||||
* Generate URLs from require module
|
||||
* @param module Module to URL
|
||||
* @return URL string
|
||||
*/
|
||||
toUrl(module: string): string;
|
||||
|
||||
/**
|
||||
* Returns true if the module has already been loaded and defined.
|
||||
* @param module Module to check
|
||||
*/
|
||||
defined(module: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the module has already been requested or is in the process of loading and should be available at some point.
|
||||
* @param module Module to check
|
||||
*/
|
||||
specified(module: string): boolean;
|
||||
|
||||
/**
|
||||
* On Error override
|
||||
* @param err
|
||||
*/
|
||||
onError(err: RequireError, errback?: (err: RequireError) => void): void;
|
||||
|
||||
/**
|
||||
* Undefine a module
|
||||
* @param module Module to undefine.
|
||||
*/
|
||||
undef(module: string): void;
|
||||
|
||||
/**
|
||||
* Semi-private function, overload in special instance of undef()
|
||||
*/
|
||||
onResourceLoad(context: Object, map: RequireMap, depArray: RequireMap[]): void;
|
||||
}
|
||||
|
||||
export interface RequireDefine {
|
||||
/**
|
||||
* Define Simple Name/Value Pairs
|
||||
* @param config Dictionary of Named/Value pairs for the config.
|
||||
*/
|
||||
(config: { [key: string]: any }): void;
|
||||
|
||||
/**
|
||||
* Define function.
|
||||
* @param func: The function module.
|
||||
*/
|
||||
(func: () => any): void;
|
||||
|
||||
/**
|
||||
* Define function with dependencies.
|
||||
* @param deps List of dependencies module IDs.
|
||||
* @param ready Callback function when the dependencies are loaded.
|
||||
* callback param deps module dependencies
|
||||
* callback return module definition
|
||||
*/
|
||||
(deps: string[], ready: Function): void;
|
||||
|
||||
/**
|
||||
* Define module with simplified CommonJS wrapper.
|
||||
* @param ready
|
||||
* callback require requirejs instance
|
||||
* callback exports exports object
|
||||
* callback module module
|
||||
* callback return module definition
|
||||
*/
|
||||
(ready: (require: Require, exports: { [key: string]: any }, module: RequireModule) => any): void;
|
||||
|
||||
/**
|
||||
* Define a module with a name and dependencies.
|
||||
* @param name The name of the module.
|
||||
* @param deps List of dependencies module IDs.
|
||||
* @param ready Callback function when the dependencies are loaded.
|
||||
* callback deps module dependencies
|
||||
* callback return module definition
|
||||
*/
|
||||
(name: string, deps: string[], ready: Function): void;
|
||||
|
||||
/**
|
||||
* Define a module with a name.
|
||||
* @param name The name of the module.
|
||||
* @param ready Callback function when the dependencies are loaded.
|
||||
* callback return module definition
|
||||
*/
|
||||
(name: string, ready: Function): void;
|
||||
|
||||
/**
|
||||
* Used to allow a clear indicator that a global define function (as needed for script src browser loading) conforms
|
||||
* to the AMD API, any global define function SHOULD have a property called "amd" whose value is an object.
|
||||
* This helps avoid conflict with any other existing JavaScript code that could have defined a define() function
|
||||
* that does not conform to the AMD API.
|
||||
* define.amd.jQuery is specific to jQuery and indicates that the loader is able to account for multiple version
|
||||
* of jQuery being loaded simultaneously.
|
||||
*/
|
||||
amd: Object;
|
||||
}
|
@ -17,35 +17,37 @@ test.describe('configure fields', () => {
|
||||
|
||||
// add fields
|
||||
await formItemInitializer.hover();
|
||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).toBeChecked();
|
||||
await page.getByRole('menuitem', { name: 'Single select', exact: true }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'Single select', exact: true }).getByRole('switch')).toBeChecked();
|
||||
|
||||
// add association fields
|
||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).toBeChecked();
|
||||
|
||||
await page.mouse.move(300, 0);
|
||||
await expect(page.getByLabel('block-item-CollectionField-general-details-general.id-ID')).toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-details-general.singleSelect-Single select'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-details-general.manyToOne.nickname'),
|
||||
).toBeVisible();
|
||||
|
||||
// delete fields
|
||||
await formItemInitializer.hover();
|
||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
||||
await expect(page.getByRole('menuitem', { name: 'ID', exact: true }).getByRole('switch')).not.toBeChecked();
|
||||
await page.getByRole('menuitem', { name: 'Single select', exact: true }).click();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Single select', exact: true }).getByRole('switch'),
|
||||
).not.toBeChecked();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
||||
await page.getByRole('menuitem', { name: 'Nickname' }).click();
|
||||
|
||||
await page.getByRole('menuitem', { name: 'Many to one' }).nth(1).hover();
|
||||
await expect(page.getByRole('menuitem', { name: 'Nickname' }).getByRole('switch')).not.toBeChecked();
|
||||
|
||||
await page.mouse.move(300, 0);
|
||||
await expect(page.getByLabel('block-item-CollectionField-general-details-general.id-ID')).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-details-general.singleSelect-Single select'),
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-details-general.manyToOne.nickname'),
|
||||
).not.toBeVisible();
|
||||
|
@ -37,6 +37,7 @@ test.describe('actions schema settings', () => {
|
||||
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
await page.mouse.move(0, 300);
|
||||
|
||||
// Edit button settings ---------------------------------------------------------------
|
||||
await expectSettingsMenu({
|
||||
|
@ -17,7 +17,7 @@ test.describe('display association fields', () => {
|
||||
// 为关系字段选中一个值
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.m2oField0-m2oField0')
|
||||
.getByLabel('Search')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
await page.getByRole('option', { name: '1', exact: true }).click();
|
||||
|
||||
@ -36,7 +36,7 @@ test.describe('display association fields', () => {
|
||||
// 为子表单中的关系字段选中一个值
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-m2oField1-form-m2oField1.m2oField1-m2oField1')
|
||||
.getByLabel('Search')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
await page.getByRole('option', { name: '1', exact: true }).click();
|
||||
|
||||
@ -137,7 +137,10 @@ test.describe('display association fields', () => {
|
||||
|
||||
// 1. 新增表单中应该显示关系字段的数据
|
||||
await page.getByRole('button', { name: 'Add new' }).click();
|
||||
await page.getByLabel('block-item-CollectionField-T2615-form-T2615.m2o-m2o').getByLabel('Search').click();
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-T2615-form-T2615.m2o-m2o')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
await page.getByRole('option', { name: '1' }).click();
|
||||
await expect(page.getByLabel('block-item-CollectionField-T2615-form-T2615.m2o.m2oOfTarget1')).toHaveText(
|
||||
`m2oOfTarget1:1`,
|
||||
|
@ -9,7 +9,7 @@ test.describe('variables with default value', () => {
|
||||
await page.getByRole('button', { name: 'Add new' }).click();
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.m2oField0-m2oField0')
|
||||
.getByLabel('Search')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
await page.getByRole('option', { name: '1', exact: true }).click();
|
||||
|
||||
|
@ -155,32 +155,32 @@ test.describe('creation form block schema settings', () => {
|
||||
await page.locator('.ant-collapse-header').nth(1).getByRole('img', { name: 'right' }).click();
|
||||
|
||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add condition', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Search Select field' }).click();
|
||||
await page.getByRole('button', { name: 'Select field' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'number' }).click();
|
||||
await page.getByLabel('Linkage rules').getByRole('spinbutton').click();
|
||||
await page.getByLabel('Linkage rules').getByRole('spinbutton').fill('123');
|
||||
|
||||
// action:使 longText 字段可编辑
|
||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||
await page.getByRole('button', { name: 'Search Select field' }).click();
|
||||
await page.getByRole('button', { name: 'Select field' }).click();
|
||||
await page.getByRole('tree').getByText('longText').click();
|
||||
await page.getByRole('button', { name: 'Search action' }).click();
|
||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||
await page.getByRole('option', { name: 'Editable' }).click();
|
||||
|
||||
// action: 为 longText 字段赋上常量值
|
||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||
await page.getByRole('button', { name: 'Search Select field' }).click();
|
||||
await page.getByRole('button', { name: 'Select field' }).click();
|
||||
await page.getByRole('tree').getByText('longText').click();
|
||||
await page.getByRole('button', { name: 'Search action' }).click();
|
||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
||||
await page.getByLabel('dynamic-component-linkage-rules').getByRole('textbox').fill('456');
|
||||
|
||||
// action: 为 integer 字段附上一个表达式,使其值等于 number 字段的值
|
||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByText('Add property').click();
|
||||
|
||||
await page.getByRole('button', { name: 'Search Select field' }).click();
|
||||
await page.getByRole('button', { name: 'Select field' }).click();
|
||||
await page.getByRole('tree').getByText('integer').click();
|
||||
await page.getByRole('button', { name: 'Search action' }).click();
|
||||
await page.getByRole('button', { name: 'action', exact: true }).click();
|
||||
await page.getByRole('option', { name: 'Value', exact: true }).click();
|
||||
await page.getByTestId('select-linkage-value-type').nth(1).click();
|
||||
await page.getByText('Expression').click();
|
||||
@ -224,7 +224,7 @@ test.describe('creation form block schema settings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('convert reference to duplicate & Save as block template', async ({ page, mockPage }) => {
|
||||
test('Save as block template & convert reference to duplicate', async ({ page, mockPage }) => {
|
||||
await mockPage(oneTableBlockWithActionsAndFormBlocks).goto();
|
||||
await page.getByRole('button', { name: 'Add new' }).click();
|
||||
|
||||
@ -248,6 +248,7 @@ test.describe('creation form block schema settings', () => {
|
||||
|
||||
// Convert reference to duplicate
|
||||
await clickOption(page, 'Convert reference to duplicate');
|
||||
// 点击之后下拉框不应该关闭,如果下拉框关闭了下面这行代码会报错
|
||||
await expect(page.getByRole('menuitem', { name: 'Save as block template' })).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: 'Convert reference to duplicate' })).not.toBeVisible();
|
||||
|
||||
@ -359,7 +360,7 @@ test.describe('creation form block schema settings', () => {
|
||||
|
||||
// 添加一个数据范围,条件是:ID = 2
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').getByLabel('Search').click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'ID', exact: true }).click();
|
||||
await page.getByLabel('Form data templates').getByRole('spinbutton').click();
|
||||
await page.getByLabel('Form data templates').getByRole('spinbutton').fill('2');
|
||||
@ -380,7 +381,7 @@ test.describe('creation form block schema settings', () => {
|
||||
// 选择一个模板
|
||||
await page.getByTestId('select-form-data-template').click();
|
||||
await page.getByRole('option', { name: 'Template name 1' }).click();
|
||||
await page.getByTestId('select-object-undefined').click();
|
||||
await page.getByTestId('select-object-single').click();
|
||||
|
||||
// 因为添加了数据范围,所以只显示一个选项
|
||||
await expect(page.getByRole('option')).toHaveCount(1);
|
||||
@ -407,7 +408,7 @@ test.describe('creation form block schema settings', () => {
|
||||
// 重新选择一下数据,字段值才会被填充
|
||||
// TODO: 保存后,数据应该直接被填充上
|
||||
await page.getByLabel('icon-close-select').click();
|
||||
await page.getByTestId('select-object-undefined').getByLabel('Search').click();
|
||||
await page.getByTestId('select-object-single').click();
|
||||
await page.getByRole('option', { name: '2' }).click();
|
||||
|
||||
await expect(
|
||||
|
@ -11,8 +11,7 @@ test('single page', async ({ page, mockPage }) => {
|
||||
await page.getByRole('menu').getByText(pageTitle1).hover();
|
||||
await page.getByLabel(pageTitle1).getByLabel('designer-schema-settings').hover();
|
||||
await page.getByRole('menuitem', { name: 'Move to' }).click();
|
||||
await page.getByRole('dialog').click();
|
||||
await page.getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-TreeSelect-Target').locator('.ant-select').click();
|
||||
await page.locator('.ant-select-dropdown').getByText(pageTitle2).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
const page1 = await page.getByRole('menu').getByText(pageTitle1).boundingBox();
|
||||
|
@ -33,7 +33,7 @@ test.describe('group page menus schema settings', () => {
|
||||
await showSettings(page, 'group page');
|
||||
await page.getByRole('menuitem', { name: 'Move to' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('dialog').getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-TreeSelect-Target').locator('.ant-select').click();
|
||||
await page.locator('.ant-select-dropdown').getByText('anchor page').click();
|
||||
await page.getByLabel('Before').click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
@ -43,7 +43,7 @@ test.describe('group page menus schema settings', () => {
|
||||
await showSettings(page, 'group page');
|
||||
await page.getByRole('menuitem', { name: 'Move to' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('dialog').getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-TreeSelect-Target').locator('.ant-select').click();
|
||||
await page.locator('.ant-select-dropdown').getByText('anchor page').click();
|
||||
await page.getByLabel('After').click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
@ -53,7 +53,7 @@ test.describe('group page menus schema settings', () => {
|
||||
await showSettings(page, 'group page');
|
||||
await page.getByRole('menuitem', { name: 'Move to' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-TreeSelect-Target').locator('.ant-select').click();
|
||||
await page.locator('.ant-select-dropdown').getByText('anchor page').click();
|
||||
await page.getByLabel('Inner').click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
@ -67,7 +67,7 @@ test.describe('group page menus schema settings', () => {
|
||||
await showSettings(page, 'a other group page');
|
||||
await page.getByRole('menuitem', { name: 'Move to' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('dialog').getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-TreeSelect-Target').locator('.ant-select').click();
|
||||
await page.getByLabel('caret-down').locator('svg').click();
|
||||
await page.locator('.ant-select-dropdown').getByText('group page', { exact: true }).click();
|
||||
await page.getByLabel('Before').click();
|
||||
|
@ -73,8 +73,15 @@ test.describe('configure actions column', () => {
|
||||
await createTable({ page, mockPage, fieldName: 'manyToOne' });
|
||||
await createActionColumn(page);
|
||||
|
||||
const expectActionsColumnWidth = async (width: number) => {
|
||||
// 移动鼠标,防止悬浮到 Actions 列,不然会导致 page.getByRole('columnheader', { name: 'Actions', exact: true }) 无效
|
||||
await page.mouse.move(0, 300);
|
||||
const box = await page.getByRole('columnheader', { name: 'Actions', exact: true }).boundingBox();
|
||||
expect(Math.floor(box.width)).toBe(width);
|
||||
};
|
||||
|
||||
// 列宽度默认为 200
|
||||
await expect(page.getByRole('columnheader', { name: 'Actions', exact: true })).toHaveJSProperty('offsetWidth', 200);
|
||||
await expectActionsColumnWidth(200);
|
||||
|
||||
await page.getByText('Actions', { exact: true }).hover();
|
||||
await page.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-users').hover();
|
||||
@ -87,11 +94,7 @@ test.describe('configure actions column', () => {
|
||||
await page.getByRole('dialog').getByRole('spinbutton').fill('400');
|
||||
await page.getByTestId('modal-Action.Modal-users-Column width').getByRole('button', { name: 'Submit' }).click();
|
||||
|
||||
// 关闭 settings 设置的下拉列表,不然获取不到宽度值
|
||||
await page.getByRole('menuitem', { name: 'Column width' }).hover();
|
||||
await page.mouse.move(300, 0);
|
||||
|
||||
await expect(page.getByRole('columnheader', { name: 'Actions', exact: true })).toHaveJSProperty('offsetWidth', 400);
|
||||
await expectActionsColumnWidth(400);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,7 @@ test('BUG: should save conditions', async ({ page, mockPage }) => {
|
||||
await mockPage(T2183).goto();
|
||||
await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click();
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').getByLabel('Search').click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'ID', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Save conditions' }).click();
|
||||
|
||||
@ -23,7 +23,7 @@ test('BUG: the input box displayed should correspond to the field type', async (
|
||||
await mockPage(T2186).goto();
|
||||
|
||||
await page.getByLabel('action-Filter.Action-Filter-filter-users-table').click();
|
||||
await page.getByTestId('select-filter-field').getByLabel('Search').click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'ID', exact: true }).click();
|
||||
|
||||
// 应该显示数字输入框
|
||||
|
@ -521,7 +521,7 @@ test.describe('actions schema settings', () => {
|
||||
// action: 禁用按钮
|
||||
await page.getByText('Add property').click();
|
||||
await page.getByLabel('block-item-ArrayCollapse-general').click();
|
||||
await page.getByTestId('select-linkage-properties').getByLabel('Search').click();
|
||||
await page.getByTestId('select-linkage-properties').click();
|
||||
await page.getByRole('option', { name: 'Disabled' }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
|
||||
@ -537,14 +537,14 @@ test.describe('actions schema settings', () => {
|
||||
|
||||
// 添加一个条件:ID 等于 1
|
||||
await page.getByRole('tabpanel').getByText('Add condition', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Search Select field' }).getByLabel('Search').click();
|
||||
await page.getByRole('button', { name: 'Select field' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'ID', exact: true }).click();
|
||||
await page.getByRole('spinbutton').click();
|
||||
await page.getByRole('spinbutton').fill('1');
|
||||
|
||||
// action: 使按钮可用
|
||||
await page.getByRole('tabpanel').getByText('Add property').click();
|
||||
await page.getByRole('combobox', { name: 'Search' }).click();
|
||||
await page.locator('.ant-select', { hasText: 'action' }).click();
|
||||
await page.getByRole('option', { name: 'Enabled' }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
|
||||
|
@ -23,7 +23,7 @@ export class LocalePlugin extends Plugin {
|
||||
const data = res?.data;
|
||||
this.locales = data?.data || {};
|
||||
this.app.use(ConfigProvider, { locale: this.locales.antd, popupMatchSelectWidth: false });
|
||||
this.app.use(App);
|
||||
this.app.use(App, { component: false });
|
||||
if (data?.data?.lang) {
|
||||
api.auth.setLocale(data?.data?.lang);
|
||||
this.app.i18n.changeLanguage(data?.data?.lang);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
|
||||
import { isPortalInBody } from '@nocobase/utils/client';
|
||||
import { App, Button, Popover } from 'antd';
|
||||
import { App, Button } from 'antd';
|
||||
import classnames from 'classnames';
|
||||
import { default as lodash } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useActionContext } from '../..';
|
||||
import { StablePopover, useActionContext } from '../..';
|
||||
import { useDesignable } from '../../';
|
||||
import { Icon } from '../../../icon';
|
||||
import { RecordProvider, useRecord } from '../../../record-provider';
|
||||
@ -187,7 +187,7 @@ Action.Popover = observer(
|
||||
(props) => {
|
||||
const { button, visible, setVisible } = useActionContext();
|
||||
return (
|
||||
<Popover
|
||||
<StablePopover
|
||||
{...props}
|
||||
destroyTooltipOnHide
|
||||
open={visible}
|
||||
@ -197,7 +197,7 @@ Action.Popover = observer(
|
||||
content={props.children}
|
||||
>
|
||||
{button}
|
||||
</Popover>
|
||||
</StablePopover>
|
||||
);
|
||||
},
|
||||
{ displayName: 'Action.Popover' },
|
||||
|
@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { ActionContext, ActionContextProvider } from '../action/context';
|
||||
import { useGetAriaLabelOfPopover } from '../action/hooks/useGetAriaLabelOfPopover';
|
||||
import { useSetAriaLabelForPopover } from '../action/hooks/useSetAriaLabelForPopover';
|
||||
import { PopoverWithStopPropagation } from '../popover';
|
||||
import { StablePopover } from '../popover';
|
||||
import { InternalNester } from './InternalNester';
|
||||
import { ReadPrettyInternalViewer } from './InternalViewer';
|
||||
import { useAssociationFieldContext } from './hooks';
|
||||
@ -52,7 +52,7 @@ export const InternaPopoverNester = observer(
|
||||
|
||||
return (
|
||||
<ActionContextProvider value={{ ...ctx, modalProps }}>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
overlayStyle={{ padding: '0px' }}
|
||||
content={content}
|
||||
trigger="click"
|
||||
@ -71,7 +71,7 @@ export const InternaPopoverNester = observer(
|
||||
</div>
|
||||
<EditOutlined style={{ display: 'inline-flex', margin: '5px' }} />
|
||||
</span>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
{visible && (
|
||||
<div
|
||||
role="button"
|
||||
|
@ -9,7 +9,7 @@ import { FormProvider, SchemaComponent } from '../../core';
|
||||
import { useDesignable } from '../../hooks';
|
||||
import { useProps } from '../../hooks/useProps';
|
||||
import { Action } from '../action';
|
||||
import { PopoverWithStopPropagation } from '../popover';
|
||||
import { StablePopover } from '../popover';
|
||||
|
||||
export const FilterActionContext = createContext<any>(null);
|
||||
|
||||
@ -28,7 +28,7 @@ export const FilterAction = observer(
|
||||
|
||||
return (
|
||||
<FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
destroyTooltipOnHide
|
||||
placement={'bottomLeft'}
|
||||
open={visible}
|
||||
@ -89,7 +89,7 @@ export const FilterAction = observer(
|
||||
}
|
||||
>
|
||||
<Action {...others} title={field.title} />
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
</FilterActionContext.Provider>
|
||||
);
|
||||
},
|
||||
|
@ -34,6 +34,11 @@ describe('Filter', () => {
|
||||
// 输入框中的默认值
|
||||
expect(inputs[0]).toHaveValue('aa');
|
||||
expect(inputs[1]).toHaveValue('aaa');
|
||||
|
||||
// 点击下拉框中的选项,Popover 不应该关闭。详见:https://nocobase.height.app/T-1508
|
||||
await userEvent.click(screen.getByText(/any/i));
|
||||
await userEvent.click(screen.getByText(/all/i));
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('default value', () => {
|
||||
@ -95,6 +100,11 @@ describe('Filter', () => {
|
||||
// 输入框中的默认值
|
||||
expect(inputs[0]).toHaveValue('');
|
||||
expect(inputs[1]).toHaveValue('aaa');
|
||||
|
||||
// 点击下拉框中的选项,Popover 不应该关闭。详见:https://nocobase.height.app/T-1508
|
||||
await userEvent.click(screen.getByText(/any/i));
|
||||
await userEvent.click(screen.getByText(/all/i));
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('dynamic options', async () => {
|
||||
|
@ -2,11 +2,11 @@ import { CloseOutlined, LoadingOutlined } from '@ant-design/icons';
|
||||
import { useFormLayout } from '@formily/antd-v5';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { isValid } from '@formily/shared';
|
||||
import { Button, Input, Space } from 'antd';
|
||||
import { Button, Space } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon, hasIcon, icons } from '../../../icon';
|
||||
import { PopoverWithStopPropagation } from '../popover';
|
||||
import { StablePopover } from '../popover';
|
||||
|
||||
function IconField(props: any) {
|
||||
const layout = useFormLayout();
|
||||
@ -16,7 +16,7 @@ function IconField(props: any) {
|
||||
return (
|
||||
<div>
|
||||
<Space.Compact>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
placement={'bottom'}
|
||||
open={visible}
|
||||
onOpenChange={(val) => {
|
||||
@ -47,7 +47,7 @@ function IconField(props: any) {
|
||||
<Button size={layout.size as any} disabled={disabled}>
|
||||
{hasIcon(value) ? <Icon type={value} /> : t('Select icon')}
|
||||
</Button>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
{value && !disabled && (
|
||||
<Button
|
||||
size={layout.size as any}
|
||||
|
@ -1,15 +1,12 @@
|
||||
import React from 'react';
|
||||
import { act, fireEvent, render } from 'testUtils';
|
||||
import { GlobalThemeProvider } from '../../../../global-theme';
|
||||
import App1 from '../demos/demo1';
|
||||
import App2 from '../demos/demo2';
|
||||
|
||||
describe('Markdown', () => {
|
||||
it('should display the value of user input', () => {
|
||||
const { container } = render(<App1 />, {
|
||||
wrapper: GlobalThemeProvider,
|
||||
});
|
||||
const textarea = container.querySelector('.ant-input') as HTMLTextAreaElement;
|
||||
const { container } = render(<App1 />);
|
||||
const textarea = container.querySelector<HTMLTextAreaElement>('.ant-input');
|
||||
act(() => {
|
||||
fireEvent.change(textarea, { target: { value: '## Hello World' } });
|
||||
});
|
||||
@ -19,10 +16,8 @@ describe('Markdown', () => {
|
||||
|
||||
describe('Markdown.Void', () => {
|
||||
it('should display the value of user input', async () => {
|
||||
const { container } = render(<App2 />, {
|
||||
wrapper: GlobalThemeProvider,
|
||||
});
|
||||
const button = container.querySelector('.ant-btn') as HTMLButtonElement;
|
||||
const { container } = render(<App2 />);
|
||||
const button = container.querySelector('.ant-btn');
|
||||
|
||||
expect(button).not.toBeNull();
|
||||
expect(container.querySelector('.ant-input')).toBeNull();
|
||||
|
@ -1,13 +1,10 @@
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from 'testUtils';
|
||||
import { GlobalThemeProvider } from '../../../../global-theme';
|
||||
import App1 from '../demos/demo1';
|
||||
|
||||
describe('Page', () => {
|
||||
it('should render correctly', async () => {
|
||||
render(<App1 />, {
|
||||
wrapper: GlobalThemeProvider,
|
||||
});
|
||||
render(<App1 />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/page title/i)).toBeInTheDocument();
|
||||
|
@ -1,16 +1,59 @@
|
||||
import { Popover as AntdPopover, PopoverProps } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
|
||||
export const PopoverWithStopPropagation = (props: PopoverProps) => {
|
||||
// 参见:https://github.com/ant-design/ant-design/issues/44119
|
||||
// fix T-1508
|
||||
/**
|
||||
* 参见:https://github.com/ant-design/ant-design/issues/44119
|
||||
* fix https://nocobase.height.app/T-1508
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const StablePopover = (props: PopoverProps) => {
|
||||
// 1. 用于记录点击的元素
|
||||
const target = useRef(null);
|
||||
|
||||
// 2. 通过 event.target 获取到被点击的元素,然后记录到 target 中
|
||||
const avoidClose = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
target.current = e.target;
|
||||
}, []);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(open: boolean) => {
|
||||
// 3. 因为 onOpenChange 会先于 onClick 触发,所以这里需要延迟执行,保证在这里能获取到被点击的元素
|
||||
setTimeout(() => {
|
||||
if (!open && target.current !== null && !isTargetInPopover(target.current)) {
|
||||
target.current = null;
|
||||
return;
|
||||
}
|
||||
props.onOpenChange?.(open);
|
||||
});
|
||||
},
|
||||
[props],
|
||||
);
|
||||
|
||||
return (
|
||||
<div onClick={avoidClose}>
|
||||
<AntdPopover {...props} />
|
||||
<div className="popover-with-stop-propagation" onClick={avoidClose}>
|
||||
<AntdPopover {...props} onOpenChange={onOpenChange} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 在 Popover 中有可能会出现被渲染在 body 的悬浮元素,这些元素会触发 Popover 的 onOpenChange 事件,导致 Popover 关闭;
|
||||
* 该方法就是用来判断被点击的元素是否在 Popover 中,如果不在,则不触发 Popover 的 onOpenChange 事件
|
||||
* @param target event.target
|
||||
* @returns
|
||||
*/
|
||||
function isTargetInPopover(target: any) {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (target) {
|
||||
if (target.classList?.contains('popover-with-stop-propagation')) {
|
||||
return true;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Field, createForm } from '@formily/core';
|
||||
import { FormContext, RecursionField, observer, useField, useFieldSchema } from '@formily/react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { useCollectionManager } from '../../../collection-manager';
|
||||
import { PopoverWithStopPropagation } from '../popover';
|
||||
import { StablePopover } from '../popover';
|
||||
|
||||
export const Editable = observer((props) => {
|
||||
const field: any = useField();
|
||||
@ -42,7 +42,7 @@ export const Editable = observer((props) => {
|
||||
|
||||
return (
|
||||
<FormItem {...props} labelStyle={{ display: 'none' }}>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
content={
|
||||
<div style={{ width: '100%', height: '100%', minWidth: 500 }}>
|
||||
<div ref={containerRef}>{modifiedChildren}</div>
|
||||
@ -62,7 +62,7 @@ export const Editable = observer((props) => {
|
||||
<RecursionField schema={schema} name={fieldSchema.name} />
|
||||
</FormContext.Provider>
|
||||
</div>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
</FormItem>
|
||||
);
|
||||
});
|
||||
|
@ -45,7 +45,7 @@ const ObjectSelect = (props: Props) => {
|
||||
<AntdSelect
|
||||
// @ts-ignore
|
||||
role="button"
|
||||
data-testid={`select-object-${mode}`}
|
||||
data-testid={`select-object-${mode || 'single'}`}
|
||||
value={toValue(value)}
|
||||
defaultValue={toValue(defaultValue)}
|
||||
allowClear={{
|
||||
|
@ -28,7 +28,9 @@ describe('Variable', () => {
|
||||
await userEvent.click(variableSelector);
|
||||
await userEvent.click(screen.getByText('v1'));
|
||||
await waitFor(() => {
|
||||
expect(input.innerHTML).toMatchInlineSnapshot('"<span class=\\"ant-tag ant-tag-blue\\" contenteditable=\\"false\\" data-variable=\\"v1\\">v1</span>"');
|
||||
expect(input.innerHTML).toMatchInlineSnapshot(
|
||||
'"<span class=\\"ant-tag ant-tag-blue\\" contenteditable=\\"false\\" data-variable=\\"v1\\">v1</span>"',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -40,13 +42,10 @@ describe('Variable', () => {
|
||||
expect(input).toBeInTheDocument();
|
||||
expect(variableSelector).toBeInTheDocument();
|
||||
|
||||
// https://testing-library.com/docs/user-event/keyboard/
|
||||
await userEvent.type(input, '{{ "a": "');
|
||||
await userEvent.click(variableSelector);
|
||||
await userEvent.click(screen.getByText('v1'));
|
||||
await userEvent.type(input, '" }');
|
||||
await userEvent.click(screen.getByRole('menuitemcheckbox', { name: 'v1' }));
|
||||
await waitFor(() => {
|
||||
expect(input.value).toMatchInlineSnapshot('"{ \\"a\\": \\"{{v1}}\\" }"');
|
||||
expect(input.value).toMatchInlineSnapshot('"{{v1}}"');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ import { Field, GeneralField, createForm } from '@formily/core';
|
||||
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { error } from '@nocobase/utils/client';
|
||||
import type { DropdownProps } from 'antd';
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
@ -157,11 +158,17 @@ export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = (props) =>
|
||||
const { Component, getMenuItems } = useMenuItem();
|
||||
const [, startTransition] = useReactTransition();
|
||||
|
||||
const changeMenu = (v: boolean) => {
|
||||
// 当鼠标快速滑过时,终止菜单的渲染,防止卡顿
|
||||
startTransition(() => {
|
||||
setVisible(v);
|
||||
});
|
||||
const changeMenu: DropdownProps['onOpenChange'] = (nextOpen: boolean, info) => {
|
||||
// 在 antd v5.8.6 版本中,点击菜单项不会触发菜单关闭,但是升级到 v5.12.2 后会触发关闭。查阅文档发现
|
||||
// 在 v5.11.0 版本中增加了一个 info.source,可以通过这个来判断一下,如果是点击的是菜单项就不关闭菜单,
|
||||
// 这样就可以和之前的行为保持一致了。
|
||||
// 下面是模仿官方文档示例做的修改:https://ant.design/components/dropdown-cn
|
||||
if (info.source === 'trigger' || nextOpen) {
|
||||
// 当鼠标快速滑过时,终止菜单的渲染,防止卡顿
|
||||
startTransition(() => {
|
||||
setVisible(nextOpen);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const items = getMenuItems(() => props.children);
|
||||
@ -171,8 +178,8 @@ export const SchemaSettingsDropdown: React.FC<SchemaSettingsProps> = (props) =>
|
||||
<Component />
|
||||
<Dropdown
|
||||
open={visible}
|
||||
onOpenChange={(open) => {
|
||||
changeMenu(open);
|
||||
onOpenChange={(open, info) => {
|
||||
changeMenu(open, info);
|
||||
}}
|
||||
overlayClassName={css`
|
||||
.ant-dropdown-menu-item-group-list {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||
import { Icon, PopoverWithStopPropagation, css } from '@nocobase/client';
|
||||
import { Icon, StablePopover, css } from '@nocobase/client';
|
||||
import type { SelectProps } from 'antd';
|
||||
import { Select as AntdSelect } from 'antd';
|
||||
import React from 'react';
|
||||
@ -33,7 +33,7 @@ const InternalSelect = connect(
|
||||
<OptGroup label={lang('Basic charts')}>
|
||||
{group1.map((option) => (
|
||||
<Option key={option.key} value={option.key} label={lang(option.title)}>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
placement={'right'}
|
||||
zIndex={99999999999}
|
||||
content={() => (
|
||||
@ -57,14 +57,14 @@ const InternalSelect = connect(
|
||||
{lang(option.title)}
|
||||
</span>
|
||||
</div>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
</Option>
|
||||
))}
|
||||
</OptGroup>
|
||||
<OptGroup label={lang('More charts')}>
|
||||
{group2.map((option) => (
|
||||
<Option key={option.key} value={option.key} label={lang(option.title)}>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
placement={'right'}
|
||||
zIndex={99999999999}
|
||||
content={() => (
|
||||
@ -88,7 +88,7 @@ const InternalSelect = connect(
|
||||
{lang(option.title)}
|
||||
</span>
|
||||
</div>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
</Option>
|
||||
))}
|
||||
</OptGroup>
|
||||
|
@ -132,7 +132,7 @@ test.describe('china region', () => {
|
||||
showMenu: () => showMenu(page, 'chinaRegion'),
|
||||
supportVariables: ['Constant', 'Current user', 'Date variables', 'Current form'],
|
||||
inputConstantValue: async () => {
|
||||
await page.getByLabel('block-item-VariableInput-').getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-VariableInput-').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '北京市' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '市辖区' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '东城区' }).click();
|
||||
@ -154,7 +154,7 @@ test.describe('china region', () => {
|
||||
expectEditable: async () => {
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion')
|
||||
.getByRole('combobox', { name: 'Search' })
|
||||
.locator('.ant-select')
|
||||
.click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '北京市' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '市辖区' }).click();
|
||||
@ -164,14 +164,16 @@ test.describe('china region', () => {
|
||||
await expect(
|
||||
page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion')
|
||||
.getByRole('combobox', { name: 'Search' }),
|
||||
.locator('.ant-select')
|
||||
.getByRole('combobox'),
|
||||
).toBeDisabled();
|
||||
},
|
||||
expectEasyReading: async () => {
|
||||
await expect(
|
||||
page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion')
|
||||
.getByRole('combobox', { name: 'Search' }),
|
||||
.locator('.ant-select')
|
||||
.getByRole('combobox'),
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion'),
|
||||
@ -311,7 +313,7 @@ test.describe('single select', () => {
|
||||
showMenu: () => showMenu(page, 'singleSelect'),
|
||||
supportVariables: ['Constant', 'Current user', 'Date variables', 'Current form'],
|
||||
inputConstantValue: async () => {
|
||||
await page.getByLabel('block-item-VariableInput-').getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-VariableInput-').click();
|
||||
await page.getByRole('option', { name: 'Option1' }).click();
|
||||
},
|
||||
expectConstantValue: async () => {
|
||||
|
@ -100,7 +100,7 @@ test.describe('many to many', () => {
|
||||
|
||||
// 配置
|
||||
await page.getByRole('button', { name: 'Add sort field' }).click();
|
||||
await page.getByTestId('select-single').getByLabel('Search').click();
|
||||
await page.getByTestId('select-single').click();
|
||||
await page.getByRole('option', { name: 'ID', exact: true }).click();
|
||||
await page.getByText('DESC', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
@ -282,7 +282,7 @@ test.describe('many to one', () => {
|
||||
// 数据应该被过滤了
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.manyToOne-manyToOne')
|
||||
.getByLabel('Search')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
await expect(page.getByRole('option', { name: String(records[0].id), exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('option')).toHaveCount(1);
|
||||
@ -486,7 +486,7 @@ test.describe('one to one (belongs to)', () => {
|
||||
// 数据应该被过滤了
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo')
|
||||
.getByLabel('Search')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
await expect(page.getByRole('option', { name: String(records[0].id), exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('option')).toHaveCount(1);
|
||||
@ -586,7 +586,7 @@ test.describe('one to one (has one)', () => {
|
||||
// 数据应该被过滤了
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.oneToOneHasOne-oneToOneHasOne')
|
||||
.getByLabel('Search')
|
||||
.getByTestId('select-object-single')
|
||||
.click();
|
||||
// 默认只显示 id 为 1 的数据,因为设置了只过滤 id 为 3 的数据,所以这里的下拉列表应该为空
|
||||
await expect(page.getByRole('option')).toHaveCount(0);
|
||||
|
@ -98,7 +98,7 @@ test.describe('china region', () => {
|
||||
expectEditable: async () => {
|
||||
await page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion')
|
||||
.getByRole('combobox', { name: 'Search' })
|
||||
.locator('.ant-select')
|
||||
.click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '北京市' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: '市辖区' }).click();
|
||||
@ -108,14 +108,16 @@ test.describe('china region', () => {
|
||||
await expect(
|
||||
page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion')
|
||||
.getByRole('combobox', { name: 'Search' }),
|
||||
.locator('.ant-select')
|
||||
.getByRole('combobox'),
|
||||
).toBeDisabled();
|
||||
},
|
||||
expectEasyReading: async () => {
|
||||
await expect(
|
||||
page
|
||||
.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion')
|
||||
.getByRole('combobox', { name: 'Search' }),
|
||||
.locator('.ant-select')
|
||||
.getByRole('combobox'),
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-form-general.chinaRegion-chinaRegion'),
|
||||
|
@ -78,7 +78,7 @@ test.describe('many to many', () => {
|
||||
|
||||
// 配置
|
||||
await page.getByRole('button', { name: 'Add sort field' }).click();
|
||||
await page.getByTestId('select-single').getByLabel('Search').click();
|
||||
await page.getByTestId('select-single').click();
|
||||
await page.getByRole('option', { name: 'ID', exact: true }).click();
|
||||
await page.getByText('DESC', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@nocobase/test/client';
|
||||
import { oneEmptyGantt } from './utils';
|
||||
import { getYmd } from '../helpers/other-helper';
|
||||
import { oneEmptyGantt } from './utils';
|
||||
const mockData = {
|
||||
singleLineText: 'within apropos leaker whoever how',
|
||||
singleLineText2: 'the inasmuch unwelcome gah hm cleverly muscle worriedly lazily',
|
||||
@ -21,7 +21,7 @@ test.describe('configure params in gantt block', () => {
|
||||
await page.getByLabel('designer-schema-settings-CardItem-Gantt.Designer-general').hover();
|
||||
await page.getByRole('menuitem', { name: 'Set the data scope' }).click();
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').getByLabel('Search').click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByTitle('ID').getByText('ID').click();
|
||||
await page.getByRole('spinbutton').fill('1');
|
||||
const [request] = await Promise.all([
|
||||
|
@ -5,10 +5,10 @@ import { uid } from '@formily/shared';
|
||||
import {
|
||||
CollectionCategroriesContext,
|
||||
CollectionProvider,
|
||||
PopoverWithStopPropagation,
|
||||
SchemaComponent,
|
||||
SchemaComponentProvider,
|
||||
Select,
|
||||
StablePopover,
|
||||
collection,
|
||||
useCollectionManager,
|
||||
useCompile,
|
||||
@ -33,8 +33,8 @@ import { CollectionNodeProvder } from './CollectionNodeProvder';
|
||||
import { ConnectAssociationAction } from './ConnectAssociationAction';
|
||||
import { ConnectChildAction } from './ConnectChildAction';
|
||||
import { ConnectParentAction } from './ConnectParentAction';
|
||||
import { EditCollectionAction } from './EditCollectionAction';
|
||||
import { DeleteCollectionAction } from './DeleteCollectionAction';
|
||||
import { EditCollectionAction } from './EditCollectionAction';
|
||||
import { EditFieldAction } from './EditFieldAction';
|
||||
import { FieldSummary } from './FieldSummary';
|
||||
import { OverrideFieldAction } from './OverrideFieldAction';
|
||||
@ -257,7 +257,7 @@ const PopoverContent = React.forwardRef((props: any, ref) => {
|
||||
}}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
content={CollectionConten(property)}
|
||||
getPopupContainer={getPopupContainer}
|
||||
mouseLeaveDelay={0}
|
||||
@ -276,7 +276,7 @@ const PopoverContent = React.forwardRef((props: any, ref) => {
|
||||
<Badge color={typeColor(property)} />
|
||||
{compile(property.uiSchema?.title)}
|
||||
</div>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
<div className={`type field_type`}>{compile(getInterface(property.interface)?.title)}</div>
|
||||
{isHovered && <OperationButton property={property} {...operatioBtnProps} />}
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { test, expect } from '@nocobase/test/client';
|
||||
import { expect, test } from '@nocobase/test/client';
|
||||
import { generalWithSingleSelect, oneEmptyKanbanBlock } from './utils';
|
||||
|
||||
//在页面中可以创建看板区块
|
||||
@ -11,7 +11,7 @@ test.describe('blockInitializers should add kanban block', () => {
|
||||
await expect(page.getByRole('menuitem', { name: 'form Kanban right' })).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'form Kanban right' }).click();
|
||||
await page.getByRole('menuitem', { name: 'General' }).click();
|
||||
await page.getByLabel('block-item-Select-Grouping field').getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-Select-Grouping field').locator('.ant-select').click();
|
||||
await page.getByRole('option', { name: 'Single select' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
await expect(page.getByLabel('block-item-CardItem-general-kanban')).toBeVisible();
|
||||
@ -102,7 +102,7 @@ test.describe('configure actions', () => {
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByLabel('schema-initializer-Grid-FormItemInitializers-general').click();
|
||||
await page.getByRole('menuitem', { name: 'Single Select' }).click();
|
||||
await page.getByLabel('Search').click();
|
||||
await page.getByLabel('block-item-CollectionField-').locator('.ant-select').click();
|
||||
await page.getByRole('option', { name: 'option1' }).click();
|
||||
await page.getByLabel('schema-initializer-ActionBar-CreateFormActionInitializers-general').hover();
|
||||
await page.getByRole('menuitem', { name: 'Submit' }).click();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { test, expect } from '@nocobase/test/client';
|
||||
import { expect, test } from '@nocobase/test/client';
|
||||
import { oneEmptyKanbanBlock } from './utils';
|
||||
|
||||
//看板的区块参数配置
|
||||
@ -35,7 +35,7 @@ test.describe('configure setting', () => {
|
||||
await page.getByLabel('designer-schema-settings-CardItem-Kanban.Designer-general').hover();
|
||||
await page.getByRole('menuitem', { name: 'Set the data scope' }).click();
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').getByLabel('Search').click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByTitle('ID').click();
|
||||
await page.getByRole('spinbutton').fill('1');
|
||||
const [request] = await Promise.all([
|
||||
|
@ -3,11 +3,11 @@ import { Form, createForm } from '@formily/core';
|
||||
import { Field, useField, useForm } from '@formily/react';
|
||||
import {
|
||||
FormProvider,
|
||||
Select,
|
||||
Input,
|
||||
PopoverWithStopPropagation,
|
||||
Radio,
|
||||
SchemaComponent,
|
||||
Select,
|
||||
StablePopover,
|
||||
locale,
|
||||
useAPIClient,
|
||||
useActionContext,
|
||||
@ -15,9 +15,9 @@ import {
|
||||
useResourceActionContext,
|
||||
useResourceContext,
|
||||
} from '@nocobase/client';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { Input as AntdInput, Button, Card, Checkbox, Col, Divider, Row, Tag, Typography, message } from 'antd';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useLocalTranslation } from './locale';
|
||||
import { localizationSchema } from './schemas/localization';
|
||||
const { Text } = Typography;
|
||||
@ -117,7 +117,7 @@ const Sync = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
placement="bottomRight"
|
||||
content={
|
||||
<>
|
||||
@ -160,7 +160,7 @@ const Sync = () => {
|
||||
>
|
||||
{t('Sync')}
|
||||
</Button>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -13,7 +13,6 @@
|
||||
"@ctrl/tinycolor": "^3.6.0",
|
||||
"@emotion/css": "^11.11.2",
|
||||
"antd": "5.x",
|
||||
"antd-token-previewer": "2.0.0-alpha.8",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash": "4.17.21",
|
||||
"rc-util": "^5.32.0",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CaretRightOutlined, ExpandOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { PopoverWithStopPropagation } from '@nocobase/client';
|
||||
import { StablePopover } from '@nocobase/client';
|
||||
import { Button, Checkbox, Collapse, ConfigProvider, Switch, Tooltip, Typography } from 'antd';
|
||||
import type { ThemeConfig } from 'antd/es/config-provider/context';
|
||||
import seed from 'antd/es/theme/themes/seed';
|
||||
@ -304,7 +304,7 @@ const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled, alph
|
||||
</Typography.Link>
|
||||
</div>
|
||||
{tokenName.startsWith('color') && (
|
||||
<PopoverWithStopPropagation
|
||||
<StablePopover
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
overlayInnerStyle={{ padding: 0 }}
|
||||
@ -326,7 +326,7 @@ const SeedTokenPreview: FC<SeedTokenProps> = ({ theme, tokenName, disabled, alph
|
||||
/>
|
||||
<div className="token-panel-pro-token-collapse-seed-block-sample-card-value">{tokenValue}</div>
|
||||
</div>
|
||||
</PopoverWithStopPropagation>
|
||||
</StablePopover>
|
||||
)}
|
||||
{['fontSize', 'sizeUnit', 'sizeStep', 'borderRadius'].includes(tokenName) && (
|
||||
<InputNumberPlus
|
||||
|
@ -1,2 +1,4 @@
|
||||
/node_modules
|
||||
/src
|
||||
/src
|
||||
/dist/node_modules/tencentcloud-sdk-nodejs/src
|
||||
/dist/node_modules/tencentcloud-sdk-nodejs/test
|
||||
|
@ -10,7 +10,7 @@ export class CreateWorkFlow {
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.name = page.getByLabel('block-item-CollectionField-workflows-Name').getByRole('textbox');
|
||||
this.triggerType = page.getByTestId('select-single').getByLabel('Search');
|
||||
this.triggerType = page.getByTestId('select-single');
|
||||
this.description = page.getByTestId('description-item').getByRole('textbox');
|
||||
this.autoDeleteHistory = page.getByTestId('select-multiple');
|
||||
}
|
||||
|
@ -38,9 +38,9 @@ test.describe('trigger collection events', () => {
|
||||
//配置工作流触发器
|
||||
await page.getByLabel(`action-WorkflowLink-Configure-workflows-${workFlowName}`).click();
|
||||
await page.getByRole('button', { name: 'Configure' }).click();
|
||||
await page.getByTestId('select-collection').getByLabel('Search').click();
|
||||
await page.getByTestId('select-collection').click();
|
||||
await page.getByText(collectionDisplayName).click();
|
||||
await page.getByTestId('select-single').getByLabel('Search').click();
|
||||
await page.getByTestId('select-single').click();
|
||||
await page.getByText('After record added', { exact: true }).click();
|
||||
await page.getByLabel('action-Action-Submit-workflows').click();
|
||||
await page.getByRole('link', { name: 'Workflow' }).click();
|
||||
|
@ -4,6 +4,8 @@ import tsConfigPaths from './tsconfig.paths.json';
|
||||
|
||||
const paths = tsConfigPaths.compilerOptions.paths;
|
||||
|
||||
const base = import.meta.url;
|
||||
|
||||
const alias = Object.keys(paths).reduce<{ find: string; replacement: string }[]>((acc, key) => {
|
||||
if (key !== '@@/*') {
|
||||
const value = paths[key][0];
|
||||
@ -15,6 +17,15 @@ const alias = Object.keys(paths).reduce<{ find: string; replacement: string }[]>
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
alias.unshift({
|
||||
find: 'packages/core/utils/src/plugin-symlink',
|
||||
replacement: 'packages/core/utils/plugin-symlink.js',
|
||||
});
|
||||
|
||||
const relativePathToAbsolute = (relativePath: string) => {
|
||||
return new URL(relativePath, base).pathname;
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
@ -29,11 +40,16 @@ export default defineConfig({
|
||||
setupFiles: 'scripts/vitest.setup.ts',
|
||||
environment: 'jsdom',
|
||||
css: false,
|
||||
threads: true,
|
||||
|
||||
alias: [
|
||||
{ find: 'testUtils', replacement: 'testUtils.ts' },
|
||||
{ find: 'testUtils', replacement: relativePathToAbsolute('./testUtils.ts') },
|
||||
{ find: /^~antd\/(.*)/, replacement: 'antd/$1' },
|
||||
...alias,
|
||||
...alias.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
replacement: relativePathToAbsolute(item.replacement),
|
||||
};
|
||||
}),
|
||||
],
|
||||
include: ['packages/**/{dumi-theme-nocobase,sdk,client,utils}/**/__tests__/**/*.{test,spec}.{ts,tsx}'],
|
||||
exclude: [
|
||||
|
Loading…
Reference in New Issue
Block a user