fix(duplicate): resolve error on click (#4658)

* fix(duplicate): resolve error on click

* test: add e2e test
This commit is contained in:
Zeke Zhang 2024-06-14 15:08:56 +08:00 committed by GitHub
parent ec22846bbb
commit 1fcf1320c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1096 additions and 552 deletions

View File

@ -51,6 +51,10 @@ export const AssociationProvider: FC<AssociationProviderProps> = (props) => {
); );
}; };
/**
* users.roles
* @returns
*/
export const useAssociationName = () => { export const useAssociationName = () => {
const field = useCollectionField(); const field = useCollectionField();
return field ? `${field.collectionName}.${field.name}` : null; return field ? `${field.collectionName}.${field.name}` : null;

View File

@ -12,22 +12,22 @@ import { RecursionField, observer, useField, useFieldSchema } from '@formily/rea
import { import {
ActionContextProvider, ActionContextProvider,
CollectionProvider_deprecated, CollectionProvider_deprecated,
RecordProvider,
FormBlockContext, FormBlockContext,
RecordProvider,
fetchTemplateData, fetchTemplateData,
useACLActionParamsContext,
useAPIClient, useAPIClient,
useActionContext, useActionContext,
useBlockRequestContext, useBlockRequestContext,
useCollectionManager_deprecated, useCollectionManager_deprecated,
useCollectionParentRecordData,
useCollection_deprecated, useCollection_deprecated,
useDesignable, useDesignable,
useFormBlockContext, useFormBlockContext,
useCollectionParentRecordData,
useRecord, useRecord,
useACLActionParamsContext,
} from '@nocobase/client'; } from '@nocobase/client';
import { App, Button } from 'antd'; import { App, Button } from 'antd';
import React, { useState, useMemo } from 'react'; import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const actionDesignerCss = css` export const actionDesignerCss = css`
@ -81,10 +81,10 @@ export const DuplicateAction = observer(
const { designable } = useDesignable(); const { designable } = useDesignable();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { service, __parent, block } = useBlockRequestContext(); const { service, __parent, block, resource } = useBlockRequestContext();
const { duplicateFields, duplicateMode = 'quickDulicate', duplicateCollection } = fieldSchema['x-component-props']; const { duplicateFields, duplicateMode = 'quickDulicate', duplicateCollection } = fieldSchema['x-component-props'];
const record = useRecord(); const record = useRecord();
const parentRecordData = useCollectionParentRecordData(); const parentRecordData: any = useCollectionParentRecordData();
const { id, __collection } = record; const { id, __collection } = record;
const ctx = useActionContext(); const ctx = useActionContext();
const { name } = useCollection_deprecated(); const { name } = useCollection_deprecated();
@ -113,7 +113,7 @@ export const DuplicateAction = observer(
setLoading(true); setLoading(true);
try { try {
const data = await fetchTemplateData(api, template, t); const data = await fetchTemplateData(api, template, t);
await api.resource(__collection || name).create({ await resource['create']({
values: { values: {
...data, ...data,
}, },
@ -200,10 +200,8 @@ export const DuplicateAction = observer(
</Button> </Button>
)} )}
<CollectionProvider_deprecated name={duplicateCollection || name}> <CollectionProvider_deprecated name={duplicateCollection || name}>
<RecordProvider {/* 这里的 record 就是弹窗中创建表单的 sourceRecord */}
record={{ ...record, __collection: duplicateCollection || __collection }} <RecordProvider record={{ ...parentRecordData, __collection: duplicateCollection || __collection }}>
parent={parentRecordData}
>
<ActionContextProvider value={{ ...ctx, visible, setVisible }}> <ActionContextProvider value={{ ...ctx, visible, setVisible }}>
<RecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties /> <RecursionField schema={fieldSchema} basePath={field.address} onlyRenderProperties />
</ActionContextProvider> </ActionContextProvider>

View File

@ -8,7 +8,7 @@
*/ */
import { expect, test } from '@nocobase/test/e2e'; import { expect, test } from '@nocobase/test/e2e';
import { oneEmptyTableBlockWithDuplicateActions } from './utils'; import { T4546, oneEmptyTableBlockWithDuplicateActions } from './templates';
test.describe('direct duplicate & copy into the form and continue to fill in', () => { test.describe('direct duplicate & copy into the form and continue to fill in', () => {
test('direct duplicate', async ({ page, mockPage, mockRecords }) => { test('direct duplicate', async ({ page, mockPage, mockRecords }) => {
@ -96,4 +96,45 @@ test.describe('direct duplicate & copy into the form and continue to fill in', (
expect(postData.oneToOneBelongsTo['id']).toBe(data.oneToOneBelongsTo['id']); expect(postData.oneToOneBelongsTo['id']).toBe(data.oneToOneBelongsTo['id']);
expect(manyToMany).toEqual(expect.arrayContaining(expectManyToMany)); expect(manyToMany).toEqual(expect.arrayContaining(expectManyToMany));
}); });
test('association block in popup', async ({ page, mockPage, mockRecord }) => {
const nocoPage = await mockPage(T4546).waitForInit();
const general1 = await mockRecord('general1');
await nocoPage.goto();
// 1. 先打开弹窗,然后点击弹窗中复制按钮,应该能成功复制,并显示在弹窗中
await page.getByLabel('action-Action.Link-View record-view-general1-table-0').click();
await page.getByLabel('action-Action.Link-Duplicate-duplicate-general2-table-0').click();
await expect(
page.getByLabel('block-item-CardItem-general2-').getByText(general1.oneToMany[0].singleLineText2),
).toHaveCount(2);
// 2. 设置复制方式为编辑模式,点击复制按钮,应该能成功复制,并显示在弹窗中
await page.getByLabel('action-Action.Link-Duplicate-duplicate-general2-table-0').hover();
await page
.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:duplicate-general2' })
.hover();
await page.getByRole('menuitem', { name: 'Duplicate mode' }).click();
await page.getByLabel('Copy into the form and').check();
await page.getByRole('button', { name: 'OK', exact: true }).click();
// 设置表单
await page.getByLabel('action-Action.Link-Duplicate-duplicate-general2-table-0').click();
await page
.getByTestId('drawer-Action.Container-general2-Duplicate')
.getByLabel('schema-initializer-Grid-popup')
.hover();
await page.getByRole('menuitem', { name: 'form Form right' }).hover();
await page.getByRole('menuitem', { name: 'Current collection' }).click();
await page
.getByTestId('drawer-Action.Container-general2-Duplicate')
.getByLabel('schema-initializer-ActionBar-')
.hover();
await page.getByRole('menuitem', { name: 'Submit' }).click();
await page.getByLabel('action-Action-Submit-submit-').click();
await expect(
page.getByLabel('block-item-CardItem-general2-').getByText(general1.oneToMany[0].singleLineText2),
).toHaveCount(3);
});
}); });

View File

@ -1,540 +0,0 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { CollectionSetting, PageConfig, general } from '@nocobase/test/e2e';
/**
* 1. general collection Percent
*/
export const generalWithDatetimeFields: CollectionSetting[] = [
{
name: 'general',
title: 'General',
fields: [
{
name: 'singleLineText',
interface: 'input',
uiSchema: {
type: 'string',
'x-component': 'Input',
title: 'Single line text',
},
},
{
name: 'startDatetime',
interface: 'datetime',
uiSchema: {
type: 'string',
'x-component': 'DatePicker',
title: 'Start date time',
required: true,
},
},
{
name: 'endDatetime',
interface: 'datetime',
uiSchema: {
type: 'string',
'x-component': 'DatePicker',
title: 'End date time',
required: true,
},
},
{
name: 'f_t22o7loai3j',
interface: 'integer',
isForeignKey: true,
uiSchema: {
type: 'number',
title: 'f_t22o7loai3j',
'x-component': 'InputNumber',
'x-read-pretty': true,
},
},
{
name: 'manyToOne',
interface: 'm2o',
foreignKey: 'f_t22o7loai3j',
uiSchema: {
'x-component': 'AssociationField',
'x-component-props': {
multiple: false,
fieldNames: {
label: 'id',
value: 'id',
},
},
title: 'Many to one',
},
target: 'users',
targetKey: 'id',
},
{
name: 'percent',
type: 'float',
interface: 'percent',
uiSchema: {
'x-component-props': {
step: '0.01',
stringMode: true,
addonAfter: '%',
},
'x-component': 'Percent',
title: 'Percent',
},
},
],
},
];
export const oneEmptyGantt: PageConfig = {
collections: generalWithDatetimeFields,
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
jebhzap4dzi: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
properties: {
gl57m4hyewf: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
jm7n5dybw6t: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
nsq0rdemz4i: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action': 'general:list',
'x-decorator': 'GanttBlockProvider',
'x-decorator-props': {
collection: 'general',
resource: 'general',
action: 'list',
fieldNames: {
id: 'id',
start: 'startDatetime',
range: 'day',
title: 'singleLineText',
end: 'endDatetime',
},
params: {
paginate: false,
},
},
'x-designer': 'Gantt.Designer',
'x-component': 'CardItem',
properties: {
zf07g7relim: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Gantt',
'x-component-props': {
useProps: '{{ useGanttBlockProps }}',
},
properties: {
toolBar: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 24,
},
},
'x-initializer': 'gantt:configureActions',
'x-uid': 'guwovmwt4c0',
'x-async': false,
'x-index': 1,
},
table: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-decorator': 'div',
'x-decorator-props': {
style: {
float: 'left',
maxWidth: '35%',
},
},
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
pagination: false,
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
'x-uid': '9in7s3pymsd',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'rgpbsjwvq2h',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'g7qwompxaeo',
'x-async': false,
'x-index': 2,
},
detail: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Gantt.Event',
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
className: 'nb-action-popup',
},
title: '{{ t("View record") }}',
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:common:addBlock',
'x-uid': 'gxtfjqzxbfu',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'zxwwx4358p1',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1p0tnmzpsim',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'hcue8v3fwti',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'fg7cjxu4hxp',
'x-async': false,
'x-index': 3,
},
},
'x-uid': 'rf47sf7k16z',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'tritqukkd86',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '3am2ctuhyka',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'p8pesbc7fiv',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ddm9fhkvrbw',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'vpk8sp3eap1',
'x-async': true,
'x-index': 1,
},
};
/**
* Table Add new / Delete / Refresh / Add record / Filter / view / edit / delete / duplicate
*/
export const oneEmptyTableBlockWithDuplicateActions: PageConfig = {
collections: general,
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
'x-index': 1,
properties: {
'1lqiou007g2': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'page:addBlock',
'x-index': 1,
properties: {
'1m4gz110aaw': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
'695oy51236d': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
twtgsvrdmn1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'general:list',
'x-decorator-props': {
collection: 'general',
resource: 'general',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
disableTemplate: false,
},
'x-designer': 'TableBlockDesigner',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'table:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
properties: {},
'x-uid': 'znrsshrlsna',
'x-async': false,
'x-index': 1,
},
'1xnl1d9j48o': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'table:configureColumns',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'table:configureItemActions',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
'659x6w2yydk': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-action': 'duplicate',
'x-acl-action': 'create',
title: '{{ t("Duplicate") }}',
'x-designer': 'Action.Designer',
'x-component': 'Action.Link',
'x-decorator': 'ACLActionProvider',
'x-component-props': {
openMode: 'drawer',
component: 'DuplicateAction',
},
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Duplicate") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Duplicate")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'popup:addNew:addBlock',
'x-uid': 'vtcnkzcaeec',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kbq4w0dmexr',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1v4k1kjpbi5',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'ok9iw50ycdh',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'sjg3udjdnc1',
'x-async': false,
'x-index': 4,
},
},
'x-uid': 'ijgo5usyzbp',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '5tnwpodzirq',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'k8t01z9qna3',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'c29q4s49svw',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '9pe6fpnq33f',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'j6g551r7tbp',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'mbw5vw7y3ea',
'x-async': false,
},
},
'x-uid': '4mbt7m7in1l',
'x-async': true,
},
};