Merge branch 'main' into next

This commit is contained in:
GitHub Actions Bot 2024-10-10 23:14:06 +00:00
commit f6db64af30
4 changed files with 118 additions and 15 deletions

View File

@ -7,7 +7,6 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import _ from 'lodash';
import React, { createContext, useCallback, useMemo } from 'react'; import React, { createContext, useCallback, useMemo } from 'react';
import { AssociationSelect } from './AssociationSelect'; import { AssociationSelect } from './AssociationSelect';
import { InternalFileManager } from './FileManager'; import { InternalFileManager } from './FileManager';
@ -29,7 +28,7 @@ export enum AssociationFieldMode {
} }
interface AssociationFieldModeProviderProps { interface AssociationFieldModeProviderProps {
modeToComponent: Partial<Record<AssociationFieldMode, React.FC | ((originalCom: React.FC) => React.FC)>>; modeToComponent: Partial<Record<AssociationFieldMode, React.FC>>;
} }
const defaultModeToComponent = { const defaultModeToComponent = {
@ -45,11 +44,15 @@ const defaultModeToComponent = {
const AssociationFieldModeContext = createContext<{ const AssociationFieldModeContext = createContext<{
modeToComponent: AssociationFieldModeProviderProps['modeToComponent']; modeToComponent: AssociationFieldModeProviderProps['modeToComponent'];
getComponent: (mode: AssociationFieldMode) => React.FC; getComponent: (mode: AssociationFieldMode) => React.FC;
getDefaultComponent: (mode: AssociationFieldMode) => React.FC;
}>({ }>({
modeToComponent: defaultModeToComponent, modeToComponent: defaultModeToComponent,
getComponent: (mode: AssociationFieldMode) => { getComponent: (mode: AssociationFieldMode) => {
return defaultModeToComponent[mode]; return defaultModeToComponent[mode];
}, },
getDefaultComponent: (mode: AssociationFieldMode) => {
return defaultModeToComponent[mode];
},
}); });
export const AssociationFieldModeProvider: React.FC<AssociationFieldModeProviderProps> = (props) => { export const AssociationFieldModeProvider: React.FC<AssociationFieldModeProviderProps> = (props) => {
@ -61,18 +64,19 @@ export const AssociationFieldModeProvider: React.FC<AssociationFieldModeProvider
const getComponent = useCallback( const getComponent = useCallback(
(mode: AssociationFieldMode) => { (mode: AssociationFieldMode) => {
const component = modeToComponent[mode]; return modeToComponent[mode] || defaultModeToComponent[mode];
if (_.isFunction(component)) {
return component(defaultModeToComponent[mode]) as React.FC;
}
return component || defaultModeToComponent[mode];
}, },
[modeToComponent], [modeToComponent],
); );
const value = useMemo(() => ({ modeToComponent, getComponent }), [getComponent, modeToComponent]); const getDefaultComponent = useCallback((mode: AssociationFieldMode) => {
return defaultModeToComponent[mode];
}, []);
const value = useMemo(
() => ({ modeToComponent, getComponent, getDefaultComponent }),
[getComponent, modeToComponent, getDefaultComponent],
);
return <AssociationFieldModeContext.Provider value={value}>{props.children}</AssociationFieldModeContext.Provider>; return <AssociationFieldModeContext.Provider value={value}>{props.children}</AssociationFieldModeContext.Provider>;
}; };

View File

@ -0,0 +1,92 @@
/**
* 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 { render, screen } from '@testing-library/react';
import React from 'react';
import { describe, expect, it, vi } from 'vitest';
import {
AssociationFieldMode,
AssociationFieldModeProvider,
useAssociationFieldModeContext,
} from '../AssociationFieldModeProvider';
vi.mock('../AssociationSelect', () => ({
AssociationSelect: () => <div>Association Select</div>,
}));
vi.mock('../InternalPicker', () => ({
InternalPicker: () => <div>Internal Picker</div>,
}));
describe('AssociationFieldModeProvider', () => {
it('should correctly provide the default modeToComponent mapping', () => {
const TestComponent = () => {
const { modeToComponent } = useAssociationFieldModeContext();
return <div>{Object.keys(modeToComponent).join(',')} </div>;
};
render(
<AssociationFieldModeProvider modeToComponent={{}}>
<TestComponent />
</AssociationFieldModeProvider>,
);
expect(screen.getByText('Picker,Nester,PopoverNester,Select,SubTable,FileManager,CascadeSelect')).toBeTruthy();
});
it('should allow overriding the default modeToComponent mapping', () => {
const CustomComponent = () => <div>Custom Component</div>;
const TestComponent = () => {
const { getComponent } = useAssociationFieldModeContext();
const Component = getComponent(AssociationFieldMode.Picker);
return <Component />;
};
render(
<AssociationFieldModeProvider modeToComponent={{ [AssociationFieldMode.Picker]: CustomComponent }}>
<TestComponent />
</AssociationFieldModeProvider>,
);
expect(screen.getByText('Custom Component')).toBeTruthy();
});
it('getComponent should return the default component if no custom component is found', () => {
const TestComponent = () => {
const { getComponent } = useAssociationFieldModeContext();
const Component = getComponent(AssociationFieldMode.Select);
return <Component />;
};
render(
<AssociationFieldModeProvider modeToComponent={{}}>
<TestComponent />
</AssociationFieldModeProvider>,
);
expect(screen.getByText('Association Select')).toBeTruthy();
});
it('getDefaultComponent should always return the default component', () => {
const CustomComponent = () => <div>Custom Component</div>;
const TestComponent = () => {
const { getDefaultComponent } = useAssociationFieldModeContext();
const Component = getDefaultComponent(AssociationFieldMode.Picker);
return <Component />;
};
render(
<AssociationFieldModeProvider modeToComponent={{ [AssociationFieldMode.Picker]: CustomComponent }}>
<TestComponent />
</AssociationFieldModeProvider>,
);
expect(screen.getByText('Internal Picker')).toBeTruthy();
});
});

View File

@ -15,7 +15,11 @@ import { Nester } from './Nester';
import { ReadPretty } from './ReadPretty'; import { ReadPretty } from './ReadPretty';
import { SubTable } from './SubTable'; import { SubTable } from './SubTable';
export { AssociationFieldModeProvider } from './AssociationFieldModeProvider'; export {
AssociationFieldMode,
AssociationFieldModeProvider,
useAssociationFieldModeContext,
} from './AssociationFieldModeProvider';
export const AssociationField: any = connect(Editable, mapReadPretty(ReadPretty)); export const AssociationField: any = connect(Editable, mapReadPretty(ReadPretty));
AssociationField.SubTable = SubTable; AssociationField.SubTable = SubTable;

View File

@ -11,16 +11,17 @@ import {
Action, Action,
AdminProvider, AdminProvider,
AntdAppProvider, AntdAppProvider,
AssociationFieldMode,
AssociationFieldModeProvider, AssociationFieldModeProvider,
BlockTemplateProvider, BlockTemplateProvider,
GlobalThemeProvider, GlobalThemeProvider,
OpenModeProvider, OpenModeProvider,
useAssociationFieldModeContext,
usePlugin, usePlugin,
} from '@nocobase/client'; } from '@nocobase/client';
import React from 'react'; import React from 'react';
import { isDesktop } from 'react-device-detect'; import { isDesktop } from 'react-device-detect';
import _ from 'lodash';
import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer'; import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer';
import { BasicZIndexProvider } from '../adaptor-of-desktop/BasicZIndexProvider'; import { BasicZIndexProvider } from '../adaptor-of-desktop/BasicZIndexProvider';
import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction'; import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction';
@ -67,9 +68,11 @@ export const Mobile = () => {
const DesktopComponent = mobilePlugin.desktopMode === false ? React.Fragment : DesktopMode; const DesktopComponent = mobilePlugin.desktopMode === false ? React.Fragment : DesktopMode;
const modeToComponent = React.useMemo(() => { const modeToComponent = React.useMemo(() => {
return { return {
PopoverNester: _.memoize((OriginComponent) => (props) => ( PopoverNester: (props) => {
<InternalPopoverNesterUsedInMobile {...props} OriginComponent={OriginComponent} /> const { getDefaultComponent } = useAssociationFieldModeContext();
)), const OriginComponent = getDefaultComponent(AssociationFieldMode.PopoverNester);
return <InternalPopoverNesterUsedInMobile {...props} OriginComponent={OriginComponent} />;
},
}; };
}, []); }, []);