Merge branch 'main' into next

This commit is contained in:
nocobase[bot] 2025-04-20 07:35:40 +00:00
commit 4dbde49b89
5 changed files with 440 additions and 2 deletions

View File

@ -309,6 +309,7 @@ test.describe('configure actions column', () => {
await page.getByText('Actions', { exact: true }).hover({ force: true });
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.mouse.move(500, 0);
// await page.getByText('Actions', { exact: true }).hover({ force: true });
// await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();

View File

@ -274,6 +274,13 @@ function getSubscriber(
// 在 FormItem 中有使用这个属性来判断字段是否被隐藏
field.data.hidden = true;
// 如果字段是必填的,并且被隐藏(保留值)了,那么就需要把 required 设置为 false否则有可能会导致表单验证失败
// 进而导致点击提交按钮无效的问题。
if (field.required) {
field.required = false;
field.data.prevRequired = true;
}
requestAnimationFrame(() => {
field.setState((state) => {
state.display = 'visible';
@ -295,6 +302,13 @@ function getSubscriber(
field.data = field.data || {};
// 在 FormItem 中有使用这个属性来判断字段是否被隐藏
field.data.hidden = false;
// 当“隐藏(保留值)”的字段再次显示时,恢复“必填”的状态
if (fieldName === 'display' && lastState?.value === 'visible' && field.data.prevRequired) {
delete field.data.prevRequired;
field.required = true;
}
requestAnimationFrame(() => {
field.setState((state) => {
state[fieldName] = lastState?.value;

View File

@ -10,6 +10,7 @@
import { expect, test } from '@nocobase/test/e2e';
import {
formFieldDependsOnSubtableFieldsWithLinkageRules,
whenARequiredFieldIsSetToHideRetainValueItShouldBeAbleToSubmitTheFormNormally,
whenClearingARelationshipFieldTheValueOfTheAssociatedFieldShouldBeCleared,
whenSetToHideRetainedValueItShouldNotImpactTheFieldSDefaultValueVariables,
} from './template';
@ -83,6 +84,28 @@ test.describe('linkage rules', () => {
).toBeVisible();
});
test('When a required field is set to "Hide (retain value)", it should be able to submit the form normally', async ({
mockPage,
page,
}) => {
await mockPage(whenARequiredFieldIsSetToHideRetainValueItShouldBeAbleToSubmitTheFormNormally).goto();
// 1. 输入昵称
await page
.getByLabel('block-item-CollectionField-users-form-users.nickname-Nickname')
.getByRole('textbox')
.fill('123456');
// 2. 点击提交
await page.getByLabel('action-Action-Submit-submit-').click();
// 3. 应该能正常提交,不应该被拦截
await page.reload();
await expect(
page.getByLabel('block-item-CardItem-users-table').getByRole('cell', { name: '123456' }),
).toBeVisible();
});
test('When clearing a relationship field, the value of the associated field should be cleared', async ({
page,
mockPage,

View File

@ -2215,3 +2215,376 @@ export const accessControlActionWithTable = {
'x-index': 1,
},
};
export const whenARequiredFieldIsSetToHideRetainValueItShouldBeAbleToSubmitTheFormNormally = {
tabSchema: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
phefo1qp4yu: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Row',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
e94p5oj6num: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Col',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
fpwdszedqqt: {
type: 'void',
version: '2.0',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:createForm',
'x-component': 'CardItem',
'x-decorator': 'FormBlockProvider',
'x-acl-action': 'users:create',
'x-app-version': '1.6.21',
'x-decorator-props': {
collection: 'users',
dataSource: 'main',
},
'x-acl-action-props': {
skipScopeCheck: true,
},
_isJSONSchemaObject: true,
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
properties: {
fy0innr5s9v: {
type: 'void',
version: '2.0',
'x-component': 'FormV2',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
'x-use-component-props': 'useCreateFormBlockProps',
properties: {
grid: {
type: 'void',
'x-uid': '7wwn4d0722h',
version: '2.0',
'x-component': 'Grid',
'x-app-version': '1.6.21',
'x-initializer': 'form:configureFields',
'x-linkage-rules': [
{
actions: [
{
operator: 'hidden',
targetFields: ['username'],
},
],
condition: {
$and: [],
},
},
],
_isJSONSchemaObject: true,
properties: {
t9ik3qkj7mv: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Row',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
szfeenblh7q: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Col',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
username: {
type: 'string',
version: '2.0',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-app-version': '1.6.21',
'x-component-props': {},
'x-collection-field': 'users.username',
_isJSONSchemaObject: true,
'x-uid': 'w7j6pbapyz2',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4p4e653i4sg',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ojqn0csar3b',
'x-async': false,
'x-index': 1,
},
ij66m020mn5: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Row',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
'95nih4mt2lf': {
type: 'void',
version: '2.0',
'x-component': 'Grid.Col',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
nickname: {
type: 'string',
version: '2.0',
'x-toolbar': 'FormItemSchemaToolbar',
'x-settings': 'fieldSettings:FormItem',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-app-version': '1.6.21',
'x-component-props': {},
'x-collection-field': 'users.nickname',
_isJSONSchemaObject: true,
'x-uid': 'rpmd8swoor7',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'h0z7u62fwn3',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'b3v741bdvtt',
'x-async': false,
'x-index': 2,
},
},
'x-async': false,
'x-index': 1,
},
o5pser9a4zy: {
type: 'void',
version: '2.0',
'x-component': 'ActionBar',
'x-app-version': '1.6.21',
'x-initializer': 'createForm:configureActions',
'x-component-props': {
layout: 'one-column',
},
_isJSONSchemaObject: true,
properties: {
g5aat11143b: {
type: 'void',
title: '{{ t("Submit") }}',
version: '2.0',
'x-action': 'submit',
'x-toolbar': 'ActionSchemaToolbar',
'x-settings': 'actionSettings:createSubmit',
'x-component': 'Action',
'x-app-version': '1.6.21',
'x-action-settings': {},
'x-component-props': {
type: 'primary',
htmlType: 'submit',
},
_isJSONSchemaObject: true,
'x-use-component-props': 'useCreateActionProps',
'x-uid': 'doarbo27x5f',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'n5j6q3dfzst',
'x-async': false,
'x-index': 2,
},
},
'x-uid': '1k05k8tv3fp',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '4bq4fts48ec',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'tgjtas03gh6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'raas2rrgtgq',
'x-async': false,
'x-index': 1,
},
gqnjk39afuj: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Row',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
fvn883pa5lt: {
type: 'void',
version: '2.0',
'x-component': 'Grid.Col',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
'0wlnuaz623s': {
type: 'void',
version: '2.0',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:table',
'x-component': 'CardItem',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-app-version': '1.6.21',
'x-filter-targets': [],
'x-decorator-props': {
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
dragSort: false,
showIndex: true,
collection: 'users',
dataSource: 'main',
},
_isJSONSchemaObject: true,
'x-use-decorator-props': 'useTableBlockDecoratorProps',
properties: {
actions: {
type: 'void',
version: '2.0',
'x-component': 'ActionBar',
'x-app-version': '1.6.21',
'x-initializer': 'table:configureActions',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
_isJSONSchemaObject: true,
'x-uid': 'q7y8z49vkvs',
'x-async': false,
'x-index': 1,
},
e7dmvrnhhhw: {
type: 'array',
version: '2.0',
'x-component': 'TableV2',
'x-app-version': '1.6.21',
'x-initializer': 'table:configureColumns',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
},
_isJSONSchemaObject: true,
'x-use-component-props': 'useTableBlockProps',
properties: {
actions: {
type: 'void',
title: '{{ t("Actions") }}',
version: '2.0',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-settings': 'fieldSettings:TableColumn',
'x-component': 'TableV2.Column',
'x-decorator': 'TableV2.Column.ActionBar',
'x-app-version': '1.6.21',
'x-initializer': 'table:configureItemActions',
'x-action-column': 'actions',
'x-toolbar-props': {
initializer: 'table:configureItemActions',
},
_isJSONSchemaObject: true,
properties: {
wwt3qrk0ro0: {
type: 'void',
version: '2.0',
'x-component': 'Space',
'x-decorator': 'DndContext',
'x-app-version': '1.6.21',
'x-component-props': {
split: '|',
},
_isJSONSchemaObject: true,
'x-uid': '58ymkonaijm',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '46zml84998i',
'x-async': false,
'x-index': 1,
},
d1qruxltt1t: {
type: 'void',
version: '2.0',
'x-toolbar': 'TableColumnSchemaToolbar',
'x-settings': 'fieldSettings:TableColumn',
'x-component': 'TableV2.Column',
'x-decorator': 'TableV2.Column.Decorator',
'x-app-version': '1.6.21',
_isJSONSchemaObject: true,
properties: {
nickname: {
version: '2.0',
'x-component': 'CollectionField',
'x-decorator': null,
'x-app-version': '1.6.21',
'x-read-pretty': true,
'x-component-props': {
ellipsis: true,
},
'x-decorator-props': {
labelStyle: {
display: 'none',
},
},
'x-collection-field': 'users.nickname',
_isJSONSchemaObject: true,
'x-uid': 'ut8m8l3qhzn',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'c2592dzb9s1',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'nntvcjy39cg',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'o5gizqch1wr',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '8awil00a9wo',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'i1dnlv9n2k0',
'x-async': false,
'x-index': 2,
},
},
name: 'ri6dthungp3',
'x-uid': 'b2u59skxpsy',
'x-async': true,
'x-index': 1,
},
};

View File

@ -31,6 +31,21 @@ function getPageMenuSchema({ pageSchemaUid, tabSchemaUid, tabSchemaName }) {
};
}
function getPageMenuSchemaWithTabSchema({ tabSchema }) {
if (!tabSchema) {
return null;
}
return {
type: 'void',
'x-component': 'Page',
properties: {
[tabSchema.name]: tabSchema,
},
'x-uid': uid(),
};
}
export * from '@playwright/test';
export { defineConfig };
@ -193,10 +208,17 @@ export interface PageConfig {
*/
collections?: CollectionSetting[];
/**
* @deprecate Schema 使
* 使 tabSchema tab Schema tabSchema
*
* Schema
* @default undefined
*/
pageSchema?: any;
/**
* Tab Schema pageSchema tabSchema tabSchema
*/
tabSchema?: any;
/** 如果为 true 则表示不会更改 PageSchema 的 uid */
keepUid?: boolean;
/** 在 URL 中的 uid例如/admin/0ig6xhe03u2 */
@ -217,6 +239,7 @@ interface CreatePageOptions {
url?: PageConfig['url'];
name?: string;
pageSchema?: any;
tabSchema?: any;
/** 如果为 true 则表示不会更改 PageSchema 的 uid */
keepUid?: boolean;
/** 在 URL 中的 uid例如/admin/0ig6xhe03u2 */
@ -367,6 +390,7 @@ export class NocoPage {
type: this.options?.type,
name: this.options?.name,
pageSchema: this.options?.pageSchema,
tabSchema: this.options?.tabSchema,
url: this.options?.url,
keepUid: this.options?.keepUid,
pageUid: this.options?.pageUid,
@ -737,13 +761,16 @@ const updateUidOfPageSchema = (uiSchema: any) => {
* NocoBase
*/
const createPage = async (options?: CreatePageOptions) => {
const { type = 'page', url, name, pageSchema, keepUid, pageUid: pageUidFromOptions } = options || {};
const { type = 'page', url, name, pageSchema, tabSchema, keepUid, pageUid: pageUidFromOptions } = options || {};
const api = await request.newContext({
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
});
const schema = getPageMenuSchemaWithTabSchema({ tabSchema }) || pageSchema;
const state = await api.storageState();
const headers = getHeaders(state);
const newPageSchema = keepUid ? pageSchema : updateUidOfPageSchema(pageSchema);
const newPageSchema = keepUid ? schema : updateUidOfPageSchema(schema);
const pageSchemaUid = newPageSchema?.['x-uid'] || uid();
const newTabSchemaUid = uid();
const newTabSchemaName = uid();