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:
Zeke Zhang 2023-12-18 15:21:57 +08:00 committed by GitHub
parent c2d95a1620
commit 6bd0568d08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 8062 additions and 5143 deletions

View File

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

View File

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

View File

@ -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",

View File

@ -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",

View File

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

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

View File

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

View File

@ -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({

View File

@ -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`,

View File

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

View File

@ -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(

View File

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

View File

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

View File

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

View File

@ -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();
// 应该显示数字输入框

View File

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

View File

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

View File

@ -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' },

View File

@ -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"

View File

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

View File

@ -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 () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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={{

View File

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

View File

@ -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 {

View File

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

View File

@ -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 () => {

View File

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

View File

@ -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'),

View File

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

View File

@ -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([

View File

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

View File

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

View File

@ -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([

View File

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

View File

@ -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",

View File

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

View File

@ -1,2 +1,4 @@
/node_modules
/src
/src
/dist/node_modules/tencentcloud-sdk-nodejs/src
/dist/node_modules/tencentcloud-sdk-nodejs/test

View File

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

View File

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

View File

@ -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: [

12443
yarn.lock

File diff suppressed because it is too large Load Diff