mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 23:16:03 +00:00
fix: unit test
This commit is contained in:
parent
3ad1a8e25f
commit
27f0b6010e
@ -614,7 +614,7 @@ class CollectionManagerV2 {
|
||||
```tsx | pure
|
||||
collectionManager.getCollectionName('users'); // 'users'
|
||||
|
||||
collectionManager.getCollectionName('users.profileId'); // 'profiles'
|
||||
collectionManager.getCollectionName('users.profiles'); // 'profiles'
|
||||
```
|
||||
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Select, Table, TableProps } from 'antd';
|
||||
import { Table, TableProps } from 'antd';
|
||||
import { SchemaComponent, UseDataBlockProps, useDataBlockRequestV2, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
|
||||
import { createApp } from '../../../collection/demos/createApp';
|
||||
import useUrlState from '@ahooksjs/use-url-state';
|
||||
|
||||
const collection = 'users';
|
||||
const associationField = 'roles';
|
||||
|
@ -0,0 +1,85 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import {
|
||||
AssociationProviderV2,
|
||||
CollectionManagerProviderV2,
|
||||
useCollectionFieldV2,
|
||||
useCollectionFieldsV2,
|
||||
useCollectionV2,
|
||||
} from '../../collection';
|
||||
import { Application } from '../../Application';
|
||||
import collections from './collections.json';
|
||||
import { SchemaComponentProvider } from '../../../schema-component';
|
||||
|
||||
function renderApp(Demo: ComponentType, props: any = {}) {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'a',
|
||||
collections: collections as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return render(
|
||||
<div data-testid="app">
|
||||
<SchemaComponentProvider designable={true}>
|
||||
<CollectionManagerProviderV2 collectionManager={app.collectionManager}>
|
||||
<AssociationProviderV2 {...props}>
|
||||
<Demo></Demo>
|
||||
</AssociationProviderV2>
|
||||
</CollectionManagerProviderV2>
|
||||
</SchemaComponentProvider>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('AssociationProvider', () => {
|
||||
test('should render', () => {
|
||||
const Demo = () => {
|
||||
const collection = useCollectionV2();
|
||||
const collectionFiled = useCollectionFieldV2();
|
||||
return (
|
||||
<>
|
||||
<div data-testid="collection">{collection.name}</div>
|
||||
<div data-testid="field">{collectionFiled.name}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderApp(Demo, { name: 'users.roles' });
|
||||
|
||||
expect(screen.getByTestId('collection')).toHaveTextContent('roles');
|
||||
expect(screen.getByTestId('field')).toHaveTextContent('roles');
|
||||
});
|
||||
|
||||
test('should render with dataSource', () => {
|
||||
const Demo = () => {
|
||||
const collection = useCollectionV2();
|
||||
const collectionFiled = useCollectionFieldV2();
|
||||
return (
|
||||
<>
|
||||
<div data-testid="collection">{collection.name}</div>
|
||||
<div data-testid="field">{collectionFiled.name}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderApp(Demo, { name: 'users.roles', dataSource: 'a' });
|
||||
|
||||
expect(screen.getByTestId('collection')).toHaveTextContent('roles');
|
||||
expect(screen.getByTestId('field')).toHaveTextContent('roles');
|
||||
});
|
||||
|
||||
test('not exists, should render `DeletedPlaceholder`', () => {
|
||||
const Demo = () => {
|
||||
return <div>children</div>;
|
||||
};
|
||||
renderApp(Demo, { name: 'users.not-exists' });
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML).not.toContain('children');
|
||||
});
|
||||
});
|
@ -0,0 +1,228 @@
|
||||
import { Application } from '../../Application';
|
||||
import { CollectionOptionsV2, DEFAULT_DATA_SOURCE_NAME } from '../../collection';
|
||||
import collections from './collections.json';
|
||||
|
||||
function getCollection(collection: CollectionOptionsV2) {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: [collection],
|
||||
},
|
||||
});
|
||||
|
||||
return app.collectionManager.getCollection(collection.name);
|
||||
}
|
||||
|
||||
describe('Collection', () => {
|
||||
describe('getPrimaryKey()', () => {
|
||||
test('Return `targetKey` if targetKey property exists', () => {
|
||||
const collection = getCollection({ name: 'test', targetKey: 'a' });
|
||||
expect(collection.getPrimaryKey()).toBe('a');
|
||||
});
|
||||
|
||||
test('If targetKey does not exist, return the name of the field with `primaryKey` set to true in the fields', () => {
|
||||
const collection = getCollection({
|
||||
name: 'test',
|
||||
fields: [{ name: 'a', primaryKey: true }, { name: 'b' }],
|
||||
});
|
||||
expect(collection.getPrimaryKey()).toBe('a');
|
||||
});
|
||||
|
||||
test('If targetKey does not exist and no field has primaryKey set to true, return `id`', () => {
|
||||
const collection = getCollection({ name: 'test' });
|
||||
expect(collection.getPrimaryKey()).toBe('id');
|
||||
});
|
||||
|
||||
test('cache the result', () => {
|
||||
const collection = getCollection({ name: 'test' });
|
||||
const spy = vitest.spyOn(collection, 'getFields');
|
||||
collection.getPrimaryKey();
|
||||
collection.getPrimaryKey();
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('titleField', () => {
|
||||
test('return `titleField` if it exists', () => {
|
||||
const collection = getCollection({ name: 'test', titleField: 'a', fields: [{ name: 'a' }] });
|
||||
expect(collection.titleField).toBe('a');
|
||||
});
|
||||
|
||||
test('if `titleField` does not exist in fields, return `primaryKey`', () => {
|
||||
const collection = getCollection({ name: 'test', titleField: 'a', targetKey: 'b' });
|
||||
expect(collection.titleField).toBe('b');
|
||||
});
|
||||
|
||||
test('if `titleField` does not exist, return `primaryKey`', () => {
|
||||
const collection = getCollection({ name: 'test', targetKey: 'a' });
|
||||
expect(collection.titleField).toBe('a');
|
||||
});
|
||||
|
||||
test('if `titleField` and `primaryKey` do not exist, return `id`', () => {
|
||||
const collection = getCollection({ name: 'test' });
|
||||
expect(collection.titleField).toBe('id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
test('getOptions()', () => {
|
||||
const collection = getCollection({ name: 'test', titleField: 'a', targetKey: 'b' });
|
||||
expect(collection.getOptions()).toMatchObject({
|
||||
name: 'test',
|
||||
titleField: 'a',
|
||||
targetKey: 'b',
|
||||
dataSource: DEFAULT_DATA_SOURCE_NAME,
|
||||
});
|
||||
});
|
||||
|
||||
test('getOption()', () => {
|
||||
const collection = getCollection({ name: 'test', key: 'a', model: 'b' });
|
||||
expect(collection.getOption('name')).toBe('test');
|
||||
expect(collection.getOption('key')).toBe('a');
|
||||
expect(collection.getOption('model')).toBe('b');
|
||||
expect(collection.getOption('dataSource')).toBe(DEFAULT_DATA_SOURCE_NAME);
|
||||
});
|
||||
});
|
||||
|
||||
test('dataSource', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'test',
|
||||
description: 'test',
|
||||
collections: [{ name: 'user' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const user = app.collectionManager.getCollection('user', { dataSource: 'test' });
|
||||
expect(user.dataSource).toBe('test');
|
||||
});
|
||||
|
||||
describe('getFields()', () => {
|
||||
test('return fields', () => {
|
||||
const collection = getCollection({ name: 'test', fields: [{ name: 'a' }, { name: 'b' }] });
|
||||
expect(collection.getFields()).toMatchObject([{ name: 'a' }, { name: 'b' }]);
|
||||
});
|
||||
|
||||
test('support predicate', () => {
|
||||
const collection = getCollection({
|
||||
name: 'test',
|
||||
fields: [
|
||||
{ name: 'a', primaryKey: false },
|
||||
{ name: 'b', primaryKey: true },
|
||||
],
|
||||
});
|
||||
|
||||
// { key: value }
|
||||
const res1 = collection.getFields({ name: 'a' });
|
||||
expect(res1.length).toBe(1);
|
||||
expect(res1[0].name).toBe('a');
|
||||
|
||||
// key === { key: true }
|
||||
const res2 = collection.getFields('primaryKey');
|
||||
expect(res2.length).toBe(1);
|
||||
expect(res2[0].name).toBe('b');
|
||||
|
||||
const res3 = collection.getFields({ primaryKey: true });
|
||||
expect(res3.length).toBe(1);
|
||||
expect(res3[0].name).toBe('b');
|
||||
|
||||
// function
|
||||
const res4 = collection.getFields((field) => field.name === 'a');
|
||||
expect(res4.length).toBe(1);
|
||||
expect(res4[0].name).toBe('a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getField()', () => {
|
||||
test('return field', () => {
|
||||
const collection = getCollection({ name: 'test', fields: [{ name: 'a' }, { name: 'b' }] });
|
||||
expect(collection.getField('a')).toMatchObject({ name: 'a' });
|
||||
|
||||
expect(collection.getField('test.a')).toMatchObject({ name: 'a' });
|
||||
});
|
||||
|
||||
test('support dot notation', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const users = app.collectionManager.getCollection('users');
|
||||
expect(users.getField('roles.name')).toMatchObject({ name: 'name', collectionName: 'roles' });
|
||||
});
|
||||
|
||||
test('return undefined if field does not exist', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const users = app.collectionManager.getCollection('users');
|
||||
|
||||
expect(users.getField('no-exist')).toBeUndefined();
|
||||
expect(users.getField('no-exist.c')).toBeUndefined();
|
||||
expect(users.getField('id.no-exist')).toBeUndefined();
|
||||
expect(users.getField('roles.no-exist')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasField()', () => {
|
||||
test('return true if field exists', () => {
|
||||
const collection = getCollection({ name: 'test', fields: [{ name: 'a' }, { name: 'b' }] });
|
||||
expect(collection.hasField('a')).toBe(true);
|
||||
});
|
||||
|
||||
test('return false if field does not exist', () => {
|
||||
const collection = getCollection({ name: 'test', fields: [{ name: 'a' }, { name: 'b' }] });
|
||||
expect(collection.hasField('c')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('properties', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const users = app.collectionManager.getCollection('users');
|
||||
const usersOptions: any = collections.find((c) => c.name === 'users');
|
||||
|
||||
expect(users.sourceKey).toBe(usersOptions.sourceKey);
|
||||
expect(users.name).toBe(usersOptions.name);
|
||||
expect(users.key).toBe(usersOptions.key);
|
||||
expect(users.title).toBe(usersOptions.title);
|
||||
expect(users.inherit).toBe(usersOptions.inherit);
|
||||
expect(users.hidden).toBe(usersOptions.hidden);
|
||||
expect(users.description).toBe(usersOptions.description);
|
||||
expect(users.duplicator).toBe(usersOptions.duplicator);
|
||||
expect(users.category).toBe(usersOptions.category);
|
||||
expect(users.targetKey).toBe(usersOptions.targetKey);
|
||||
expect(users.model).toBe(usersOptions.model);
|
||||
expect(users.createdBy).toBe(usersOptions.createdBy);
|
||||
expect(users.updatedBy).toBe(usersOptions.updatedBy);
|
||||
expect(users.logging).toBe(usersOptions.logging);
|
||||
expect(users.from).toBe(usersOptions.from);
|
||||
expect(users.rawTitle).toBe(usersOptions.rawTitle);
|
||||
expect(users.isLocal).toBe(usersOptions.isLocal);
|
||||
expect(users.inherits).toMatchObject([]);
|
||||
expect(users.sources).toMatchObject([]);
|
||||
expect(users.fields).toMatchObject(usersOptions.fields);
|
||||
expect(users.tableName).toBe(usersOptions.tableName);
|
||||
expect(users.viewName).toBe(usersOptions.viewName);
|
||||
expect(users.writableView).toBe(usersOptions.writableView);
|
||||
expect(users.filterTargetKey).toBe(usersOptions.filterTargetKey);
|
||||
expect(users.sortable).toBe(usersOptions.sortable);
|
||||
expect(users.autoGenId).toBe(usersOptions.autoGenId);
|
||||
expect(users.magicAttribute).toBe(usersOptions.magicAttribute);
|
||||
expect(users.tree).toBe(usersOptions.tree);
|
||||
expect(users.isThrough).toBe(usersOptions.isThrough);
|
||||
expect(users.autoCreate).toBe(usersOptions.autoCreate);
|
||||
expect(users.resource).toBe(usersOptions.resource);
|
||||
});
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import { CollectionFieldV2, CollectionManagerProviderV2, CollectionProviderV2 } from '../../collection';
|
||||
import { Application } from '../../Application';
|
||||
import collections from './collections.json';
|
||||
import { FormItem, Input, SchemaComponent, SchemaComponentProvider } from '../../../schema-component';
|
||||
|
||||
function renderApp() {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const schema = {
|
||||
name: 'root',
|
||||
type: 'object',
|
||||
properties: {
|
||||
nickname: {
|
||||
'x-component': 'CollectionField',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return render(
|
||||
<div data-testid="app">
|
||||
<SchemaComponentProvider designable={true}>
|
||||
<CollectionManagerProviderV2 collectionManager={app.collectionManager}>
|
||||
<CollectionProviderV2 name="users">
|
||||
<SchemaComponent schema={schema} components={{ CollectionField: CollectionFieldV2, FormItem, Input }} />
|
||||
</CollectionProviderV2>
|
||||
</CollectionManagerProviderV2>
|
||||
</SchemaComponentProvider>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('CollectionField', () => {
|
||||
it('works', async () => {
|
||||
renderApp();
|
||||
expect(screen.getByText('Nickname')).toBeInTheDocument();
|
||||
expect(screen.getByRole('textbox')).toHaveClass('ant-input');
|
||||
});
|
||||
});
|
@ -0,0 +1,60 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import {
|
||||
CollectionFieldProviderV2,
|
||||
CollectionManagerProviderV2,
|
||||
CollectionProviderV2,
|
||||
useCollectionFieldV2,
|
||||
} from '../../collection';
|
||||
import { Application } from '../../Application';
|
||||
import collections from './collections.json';
|
||||
import { SchemaComponentProvider } from '../../../schema-component';
|
||||
|
||||
function renderApp(Demo: ComponentType, name?: string) {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
return render(
|
||||
<div data-testid="app">
|
||||
<SchemaComponentProvider designable={true}>
|
||||
<CollectionManagerProviderV2 collectionManager={app.collectionManager}>
|
||||
<CollectionProviderV2 name="users">
|
||||
<CollectionFieldProviderV2 name={name}>
|
||||
<Demo></Demo>
|
||||
</CollectionFieldProviderV2>
|
||||
</CollectionProviderV2>
|
||||
</CollectionManagerProviderV2>
|
||||
</SchemaComponentProvider>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('CollectionFieldProvider', () => {
|
||||
test('useCollectionFieldV2() should get current field', () => {
|
||||
const Demo = () => {
|
||||
const field = useCollectionFieldV2();
|
||||
return (
|
||||
<>
|
||||
<div data-testid="demo">{field.name}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderApp(Demo, 'nickname');
|
||||
|
||||
expect(screen.getByTestId('demo')).toHaveTextContent('nickname');
|
||||
});
|
||||
|
||||
test('field not exists, should render `DeletedPlaceholder`', () => {
|
||||
const Demo = () => {
|
||||
return <div>children</div>;
|
||||
};
|
||||
|
||||
renderApp(Demo, 'not-exists');
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML).toContain('ant-result');
|
||||
expect(screen.getByTestId('app').innerHTML).not.toContain('children');
|
||||
});
|
||||
});
|
@ -0,0 +1,928 @@
|
||||
import {
|
||||
Application,
|
||||
CollectionFieldInterfaceBase,
|
||||
CollectionTemplateBase,
|
||||
CollectionV2,
|
||||
DEFAULT_DATA_SOURCE_NAME,
|
||||
Plugin,
|
||||
} from '@nocobase/client';
|
||||
import collections from './collections.json';
|
||||
|
||||
describe('CollectionManager', () => {
|
||||
const collectionLength = collections.length;
|
||||
|
||||
describe('collections', () => {
|
||||
describe('init', () => {
|
||||
test('init should work', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
});
|
||||
|
||||
test('plugin should work', async () => {
|
||||
class MyPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.collectionManager.addCollections(collections as any);
|
||||
}
|
||||
}
|
||||
const app = new Application({
|
||||
plugins: [MyPlugin],
|
||||
});
|
||||
await app.load();
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
});
|
||||
|
||||
test('collection should be instantiated', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const collectionInstances = app.collectionManager.getCollections();
|
||||
collectionInstances.forEach((collection) => {
|
||||
expect(collection).toBeInstanceOf(CollectionV2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addCollection()', () => {
|
||||
test('addCollections(collections)', async () => {
|
||||
class MyPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.collectionManager.addCollections(collections as any);
|
||||
}
|
||||
}
|
||||
const app = new Application({
|
||||
plugins: [MyPlugin],
|
||||
});
|
||||
await app.load();
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
});
|
||||
|
||||
test('addCollections(collections) should deduplicate when adding collections', () => {
|
||||
const app = new Application();
|
||||
app.collectionManager.addCollections([collections[0]] as any);
|
||||
app.collectionManager.addCollections([collections[1]] as any);
|
||||
app.collectionManager.addCollections(collections as any);
|
||||
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
});
|
||||
|
||||
test('addCollections(collections, { dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
app.collectionManager.addCollections(collections as any, { dataSource: 'a' });
|
||||
expect(app.collectionManager.getCollections({ dataSource: 'a' }).length).toBe(collectionLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCollections()', () => {
|
||||
test('setCollections(collections) will reset the corresponding data source content', () => {
|
||||
const app = new Application();
|
||||
app.collectionManager.setCollections(collections as any);
|
||||
app.collectionManager.setCollections([collections[1] as any]);
|
||||
|
||||
const collectionInstances = app.collectionManager.getCollections();
|
||||
expect(collectionInstances.length).toBe(1);
|
||||
expect(collectionInstances[0].name).toBe(collections[1]['name']);
|
||||
});
|
||||
|
||||
test('setCollections(collections, { dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
app.collectionManager.addCollections(collections as any, { dataSource: 'a' });
|
||||
app.collectionManager.setCollections([collections[1]] as any, { dataSource: 'a' });
|
||||
|
||||
const collectionInstances = app.collectionManager.getCollections({ dataSource: 'a' });
|
||||
expect(collectionInstances.length).toBe(1);
|
||||
expect(collectionInstances[0].name).toBe(collections[1]['name']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollections()', () => {
|
||||
test('getCollections({ dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
app.collectionManager.addCollections(collections as any);
|
||||
app.collectionManager.addCollections(collections as any, { dataSource: 'a' });
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
expect(app.collectionManager.getCollections({ dataSource: 'a' }).length).toBe(collectionLength);
|
||||
});
|
||||
|
||||
test('getCollections({ predicate })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
expect(
|
||||
app.collectionManager.getCollections({
|
||||
predicate: (collection) => collection.name === collections[0]['name'],
|
||||
}).length,
|
||||
).toBe(1);
|
||||
expect(
|
||||
app.collectionManager.getCollections({ predicate: (collection) => collection.hidden === false }).length,
|
||||
).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllCollections', () => {
|
||||
test('getAllCollections() should work', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
app.collectionManager.addCollections(collections as any);
|
||||
app.collectionManager.addCollections([collections[0]] as any, { dataSource: 'a' });
|
||||
|
||||
const allCollections = app.collectionManager.getAllCollections();
|
||||
|
||||
expect(allCollections.length).toBe(2);
|
||||
expect(allCollections[0].name).toBe(DEFAULT_DATA_SOURCE_NAME);
|
||||
expect(allCollections[1].name).toBe('a');
|
||||
|
||||
expect(allCollections[0].collections.length).toBe(2);
|
||||
expect(allCollections[1].collections.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollection()', () => {
|
||||
test('getCollection("collectionName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collection = app.collectionManager.getCollection('users');
|
||||
expect(collection instanceof CollectionV2).toBeTruthy();
|
||||
expect(collection.name).toBe('users');
|
||||
});
|
||||
test('getCollection("collectionName.associationFieldName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collection = app.collectionManager.getCollection('users.roles');
|
||||
expect(collection instanceof CollectionV2).toBeTruthy();
|
||||
expect(collection.name).toBe('roles');
|
||||
});
|
||||
|
||||
test('getCollection(object) should return an instance without performing a lookup', () => {
|
||||
const app = new Application();
|
||||
const collection = app.collectionManager.getCollection(collections[0] as any);
|
||||
expect(collection instanceof CollectionV2).toBeTruthy();
|
||||
expect(collection.name).toBe('users');
|
||||
});
|
||||
|
||||
test('getCollection("collectionName", { dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
app.collectionManager.addCollections(collections as any, { dataSource: 'a' });
|
||||
const collection = app.collectionManager.getCollection('users', { dataSource: 'a' });
|
||||
expect(collection instanceof CollectionV2).toBeTruthy();
|
||||
expect(collection.name).toBe('users');
|
||||
});
|
||||
|
||||
test('getCollection("not-exists") should return undefined', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collection1 = app.collectionManager.getCollection('not-exists');
|
||||
const collection2 = app.collectionManager.getCollection('users.not-exists');
|
||||
expect(collection1).toBeUndefined();
|
||||
expect(collection2).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getCollection(undefined) should return undefined', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collection = app.collectionManager.getCollection(undefined);
|
||||
expect(collection).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollectionName()', () => {
|
||||
test('getCollectionName("collectionName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collectionName = app.collectionManager.getCollectionName('users');
|
||||
expect(collectionName).toBe('users');
|
||||
});
|
||||
test('getCollectionName("collectionName.associationFieldName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collectionName = app.collectionManager.getCollectionName('users.roles');
|
||||
expect(collectionName).toBe('roles');
|
||||
});
|
||||
test('getCollectionName("collectionName", { dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
app.collectionManager.addCollections(collections as any, { dataSource: 'a' });
|
||||
const collectionName = app.collectionManager.getCollectionName('users', { dataSource: 'a' });
|
||||
expect(collectionName).toBe('users');
|
||||
});
|
||||
test('getCollectionName("not-exists") should return undefined', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const collection1 = app.collectionManager.getCollectionName('not-exists');
|
||||
const collection2 = app.collectionManager.getCollectionName('users.not-exists');
|
||||
expect(collection1).toBeUndefined();
|
||||
expect(collection2).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollectionField()', () => {
|
||||
test('getCollectionField("collectionName.fieldName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const field = app.collectionManager.getCollectionField('users.nickname');
|
||||
expect(field).toBeTruthy();
|
||||
expect(field.name).toBe('nickname');
|
||||
});
|
||||
|
||||
test('getCollectionField("collectionName.associationFieldName.fieldName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const field = app.collectionManager.getCollectionField('users.roles.name');
|
||||
expect(field).toBeTruthy();
|
||||
expect(field.name).toBe('name');
|
||||
expect(field.collectionName).toBe('roles');
|
||||
});
|
||||
|
||||
test('getCollectionField("collectionName.fieldName", { dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
collections: collections as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const field = app.collectionManager.getCollectionField('users.nickname', { dataSource: 'a' });
|
||||
expect(field).toBeTruthy();
|
||||
expect(field.name).toBe('nickname');
|
||||
});
|
||||
|
||||
test('getCollectionField("not-exists") should return undefined', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const field1 = app.collectionManager.getCollectionField('not-exists');
|
||||
const field2 = app.collectionManager.getCollectionField('users.not-exists');
|
||||
const field3 = app.collectionManager.getCollectionField('not-exists.not-exists');
|
||||
expect(field1).toBeUndefined();
|
||||
expect(field2).toBeUndefined();
|
||||
expect(field3).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getCollectionField(undefined) should return undefined', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const field = app.collectionManager.getCollectionField(undefined);
|
||||
expect(field).toBeUndefined();
|
||||
});
|
||||
|
||||
test('getCollectionField(object) should return object', () => {
|
||||
const app = new Application();
|
||||
const obj = {
|
||||
name: 'nickname',
|
||||
type: 'string',
|
||||
};
|
||||
|
||||
const field = app.collectionManager.getCollectionField(obj);
|
||||
expect(field).toEqual(obj);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCollectionFields()', () => {
|
||||
const roles = collections.find((item) => item.name === 'roles');
|
||||
const users = collections.find((item) => item.name === 'users');
|
||||
test('getCollectionFields("collectionName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const fields = app.collectionManager.getCollectionFields('users');
|
||||
expect(fields).toEqual(users.fields);
|
||||
});
|
||||
|
||||
test('getCollectionFields("collectionName", { dataSource })', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'A',
|
||||
collections: collections as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const fields = app.collectionManager.getCollectionFields('users', { dataSource: 'a' });
|
||||
expect(fields).toEqual(users.fields);
|
||||
});
|
||||
|
||||
test('getCollectionFields("collectionName.associationFieldName")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const fields = app.collectionManager.getCollectionFields('users.roles');
|
||||
expect(fields).toEqual(roles.fields);
|
||||
});
|
||||
|
||||
test('getCollectionFields("not-exists")', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
const fields = app.collectionManager.getCollectionFields('not-exists');
|
||||
expect(Array.isArray(fields)).toBeTruthy();
|
||||
expect(fields.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('mixins', () => {
|
||||
test('init should work', () => {
|
||||
class DemoCollectionMixin extends CollectionV2 {
|
||||
a() {
|
||||
return 'test-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
collectionMixins: [DemoCollectionMixin],
|
||||
},
|
||||
});
|
||||
|
||||
const user = app.collectionManager.getCollection<DemoCollectionMixin>('users');
|
||||
expect(user.a()).toBe('test-users');
|
||||
});
|
||||
|
||||
test('plugin should work', async () => {
|
||||
class DemoCollectionMixin extends CollectionV2 {
|
||||
b() {
|
||||
return 'test-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.collectionManager.addCollections(collections as any);
|
||||
this.app.collectionManager.addCollectionMixins([DemoCollectionMixin]);
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
plugins: [MyPlugin],
|
||||
});
|
||||
|
||||
await app.load();
|
||||
|
||||
const user = app.collectionManager.getCollection<DemoCollectionMixin>('users');
|
||||
expect(user.b()).toBe('test-users');
|
||||
});
|
||||
|
||||
test('multiple mixins should work', () => {
|
||||
class DemoCollectionMixin1 extends CollectionV2 {
|
||||
c() {
|
||||
return 'test1-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
class DemoCollectionMixin2 extends CollectionV2 {
|
||||
d() {
|
||||
return 'test2-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
collectionMixins: [DemoCollectionMixin1, DemoCollectionMixin2],
|
||||
},
|
||||
});
|
||||
|
||||
const user = app.collectionManager.getCollection<DemoCollectionMixin1 & DemoCollectionMixin2>('users');
|
||||
expect(user.c()).toBe('test1-users');
|
||||
expect(user.d()).toBe('test2-users');
|
||||
});
|
||||
|
||||
test('after add mixins, collection should be re-instantiated', () => {
|
||||
class DemoCollectionMixin extends CollectionV2 {
|
||||
e() {
|
||||
return 'test-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const user = app.collectionManager.getCollection<DemoCollectionMixin>('users');
|
||||
expect(user.e).toBeUndefined();
|
||||
|
||||
app.collectionManager.addCollectionMixins([DemoCollectionMixin]);
|
||||
|
||||
const user2 = app.collectionManager.getCollection<DemoCollectionMixin>('users');
|
||||
expect(user2.e()).toBe('test-users');
|
||||
});
|
||||
});
|
||||
|
||||
describe('templates', () => {
|
||||
test('init should work', () => {
|
||||
class DemoTemplate extends CollectionTemplateBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collectionTemplates: [DemoTemplate],
|
||||
},
|
||||
});
|
||||
|
||||
const templates = app.collectionManager.getCollectionTemplates();
|
||||
expect(templates.length).toBe(1);
|
||||
expect(templates[0].name).toBe('demo');
|
||||
});
|
||||
|
||||
test('plugin should work', async () => {
|
||||
class DemoTemplate extends CollectionTemplateBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.collectionManager.addCollectionTemplates([DemoTemplate]);
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
plugins: [MyPlugin],
|
||||
});
|
||||
|
||||
await app.load();
|
||||
|
||||
const templates = app.collectionManager.getCollectionTemplates();
|
||||
expect(templates.length).toBe(1);
|
||||
expect(templates[0].name).toBe('demo');
|
||||
});
|
||||
|
||||
test('If the Template has a Collection property and the template property of collections is equal to Template.name, use the custom Collection for initialization', () => {
|
||||
class CustomCollection extends CollectionV2 {
|
||||
custom() {
|
||||
return 'custom-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
class DemoTemplate extends CollectionTemplateBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
Collection = CustomCollection;
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections.map((item) => ({ ...item, template: 'demo' })) as any,
|
||||
collectionTemplates: [DemoTemplate],
|
||||
},
|
||||
});
|
||||
|
||||
const user = app.collectionManager.getCollection<CustomCollection>('users');
|
||||
expect(user.custom()).toBe('custom-users');
|
||||
});
|
||||
|
||||
test('after add templates, collection should be re-instantiated', () => {
|
||||
class CustomCollection extends CollectionV2 {
|
||||
custom() {
|
||||
return 'custom-' + this.name;
|
||||
}
|
||||
}
|
||||
|
||||
class DemoTemplate extends CollectionTemplateBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
Collection = CustomCollection;
|
||||
}
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections.map((item) => ({ ...item, template: 'demo' })) as any,
|
||||
},
|
||||
});
|
||||
|
||||
const user = app.collectionManager.getCollection<CustomCollection>('users');
|
||||
expect(user.custom).toBeUndefined();
|
||||
|
||||
app.collectionManager.addCollectionTemplates([DemoTemplate]);
|
||||
|
||||
const user2 = app.collectionManager.getCollection<CustomCollection>('users');
|
||||
expect(user2.custom()).toBe('custom-users');
|
||||
});
|
||||
|
||||
test('getCollectionTemplate', () => {
|
||||
class DemoTemplate extends CollectionTemplateBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collectionTemplates: [DemoTemplate],
|
||||
},
|
||||
});
|
||||
|
||||
const template = app.collectionManager.getCollectionTemplate('demo');
|
||||
expect(template.name).toBe('demo');
|
||||
});
|
||||
|
||||
test('transformCollection', () => {
|
||||
const mockFn = vitest.fn();
|
||||
|
||||
class DemoTemplate extends CollectionTemplateBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
transform(collection) {
|
||||
mockFn(collection);
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
new Application({
|
||||
collectionManager: {
|
||||
collections: collections.map((item) => ({ ...item, template: 'demo' })) as any,
|
||||
collectionTemplates: [DemoTemplate],
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockFn).toBeCalledTimes(collectionLength);
|
||||
});
|
||||
});
|
||||
|
||||
describe('field interface', () => {
|
||||
test('init should work', () => {
|
||||
class DemoFieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
fieldInterfaces: [DemoFieldInterface],
|
||||
},
|
||||
});
|
||||
|
||||
const fieldInterfaces = app.collectionManager.getFieldInterfaces();
|
||||
expect(Object.keys(fieldInterfaces).length).toBe(1);
|
||||
expect(fieldInterfaces.demo instanceof DemoFieldInterface).toBeTruthy();
|
||||
});
|
||||
|
||||
test('plugin should work', async () => {
|
||||
class DemoFieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.collectionManager.addFieldInterfaces([DemoFieldInterface]);
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
plugins: [MyPlugin],
|
||||
});
|
||||
|
||||
await app.load();
|
||||
|
||||
const fieldInterfaces = app.collectionManager.getFieldInterfaces();
|
||||
expect(Object.keys(fieldInterfaces).length).toBe(1);
|
||||
expect(fieldInterfaces.demo instanceof DemoFieldInterface).toBeTruthy();
|
||||
});
|
||||
|
||||
test('getFieldInterface()', () => {
|
||||
class DemoFieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
fieldInterfaces: [DemoFieldInterface],
|
||||
},
|
||||
});
|
||||
|
||||
const fieldInterface = app.collectionManager.getFieldInterface('demo');
|
||||
expect(fieldInterface.name).toBe('demo');
|
||||
expect(fieldInterface instanceof DemoFieldInterface).toBeTruthy();
|
||||
});
|
||||
|
||||
test('getFieldInterfaces()', () => {
|
||||
class DemoFieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo';
|
||||
title = 'Demo';
|
||||
}
|
||||
class Demo2FieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo2';
|
||||
title = 'Demo';
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
fieldInterfaces: [DemoFieldInterface, Demo2FieldInterface],
|
||||
},
|
||||
});
|
||||
|
||||
const fieldInterfaces = app.collectionManager.getFieldInterfaces();
|
||||
expect(Object.keys(fieldInterfaces).length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('field groups', () => {
|
||||
test('init add', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
fieldGroups: {
|
||||
demo: {
|
||||
label: 'Demo',
|
||||
order: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const fieldGroups = app.collectionManager.getFieldGroups();
|
||||
expect(Object.keys(fieldGroups).length).toBe(1);
|
||||
expect(fieldGroups.demo).toBeTruthy();
|
||||
});
|
||||
|
||||
test('plugin add', async () => {
|
||||
class MyPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.collectionManager.addFieldGroups({
|
||||
demo: {
|
||||
label: 'Demo',
|
||||
order: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
plugins: [MyPlugin],
|
||||
});
|
||||
|
||||
await app.load();
|
||||
|
||||
const fieldGroups = app.collectionManager.getFieldGroups();
|
||||
expect(Object.keys(fieldGroups).length).toBe(1);
|
||||
expect(fieldGroups.demo).toBeTruthy();
|
||||
});
|
||||
|
||||
test('getFieldGroup(name)', () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
fieldGroups: {
|
||||
demo: {
|
||||
label: 'Demo',
|
||||
order: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const fieldGroup = app.collectionManager.getFieldGroup('demo');
|
||||
expect(fieldGroup.label).toBe('Demo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dataSource and reload', () => {
|
||||
test('reload main', async () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: [collections[0]] as any,
|
||||
},
|
||||
});
|
||||
const mainDataSourceFn = () => {
|
||||
return Promise.resolve([collections[1]] as any);
|
||||
};
|
||||
|
||||
const mockFn = vitest.fn();
|
||||
const mockFn2 = vitest.fn();
|
||||
|
||||
app.collectionManager.setMainDataSource(mainDataSourceFn);
|
||||
app.collectionManager.addReloadCallback(mockFn);
|
||||
|
||||
await app.collectionManager.reloadMain(mockFn2);
|
||||
|
||||
const collectionInstances = app.collectionManager.getCollections();
|
||||
expect(collectionInstances.length).toBe(1);
|
||||
expect(collectionInstances[0].name).toBe(collections[1]['name']);
|
||||
expect(mockFn).toBeCalledTimes(1);
|
||||
expect(mockFn2).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('reload third dataSource', async () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
|
||||
const dataSourceFn = () => {
|
||||
return Promise.resolve([
|
||||
{
|
||||
name: 'a',
|
||||
description: 'a',
|
||||
collections: collections as any,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const mockFn = vitest.fn();
|
||||
const mockFn2 = vitest.fn();
|
||||
|
||||
app.collectionManager.setThirdDataSource(dataSourceFn);
|
||||
app.collectionManager.addReloadCallback(mockFn, 'a');
|
||||
|
||||
await app.collectionManager.reloadThirdDataSource(mockFn2);
|
||||
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
expect(app.collectionManager.getCollections({ dataSource: 'a' }).length).toBe(collectionLength);
|
||||
expect(app.collectionManager.getDataSources().length).toBe(2);
|
||||
expect(app.collectionManager.getDataSource('a').name).toBe('a');
|
||||
|
||||
expect(mockFn).toBeCalledTimes(1);
|
||||
expect(mockFn2).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('reload all', async () => {
|
||||
const app = new Application();
|
||||
|
||||
app.collectionManager.setMainDataSource(() => {
|
||||
return Promise.resolve(collections as any);
|
||||
});
|
||||
app.collectionManager.setThirdDataSource(() => {
|
||||
return Promise.resolve([
|
||||
{
|
||||
name: 'a',
|
||||
description: 'a',
|
||||
collections: collections as any,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const mockFn = vitest.fn();
|
||||
const mockFn2 = vitest.fn();
|
||||
|
||||
app.collectionManager.addReloadCallback(mockFn);
|
||||
app.collectionManager.addReloadCallback(mockFn, 'a');
|
||||
await app.collectionManager.reloadAll(mockFn2);
|
||||
|
||||
expect(app.collectionManager.getCollections().length).toBe(collectionLength);
|
||||
expect(app.collectionManager.getCollections({ dataSource: 'a' }).length).toBe(collectionLength);
|
||||
expect(app.collectionManager.getDataSources().length).toBe(2);
|
||||
expect(app.collectionManager.getDataSource('a').name).toBe('a');
|
||||
|
||||
expect(mockFn).toBeCalledTimes(2);
|
||||
expect(mockFn2).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('not set reload fn', async () => {
|
||||
const app = new Application();
|
||||
|
||||
const mockFn = vitest.fn();
|
||||
const mockFn2 = vitest.fn();
|
||||
|
||||
app.collectionManager.addReloadCallback(mockFn);
|
||||
app.collectionManager.addReloadCallback(mockFn, 'a');
|
||||
await app.collectionManager.reloadAll(mockFn2);
|
||||
|
||||
expect(mockFn).toBeCalledTimes(0);
|
||||
expect(mockFn2).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inherit', () => {
|
||||
test('inherit', async () => {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: [collections[0]] as any,
|
||||
},
|
||||
});
|
||||
|
||||
const mockFn = vitest.fn();
|
||||
|
||||
const mainDataSourceFn = () => {
|
||||
return Promise.resolve([collections[0]] as any);
|
||||
};
|
||||
app.collectionManager.setMainDataSource(mainDataSourceFn);
|
||||
|
||||
const cm = app.collectionManager.inherit({
|
||||
collections: [collections[1]] as any,
|
||||
reloadCallback: mockFn,
|
||||
});
|
||||
|
||||
expect(cm.getCollections().length).toBe(2);
|
||||
|
||||
await cm.reloadAll();
|
||||
expect(cm.getCollections().length).toBe(1);
|
||||
expect(mockFn).toBeCalledTimes(1);
|
||||
|
||||
expect(app.collectionManager.getCollections().length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,80 @@
|
||||
import React, { ComponentType, useEffect } from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import {
|
||||
CollectionManagerProviderV2,
|
||||
CollectionManagerV2,
|
||||
useCollectionManagerV2,
|
||||
useCollectionsV2,
|
||||
} from '../../collection';
|
||||
import { Application } from '../../Application';
|
||||
import collections from './collections.json';
|
||||
|
||||
function renderApp(Demo: ComponentType) {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
dataSources: [
|
||||
{
|
||||
name: 'a',
|
||||
description: 'a',
|
||||
collections: [collections[0]] as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return render(
|
||||
<CollectionManagerProviderV2 collectionManager={app.collectionManager}>
|
||||
<Demo></Demo>
|
||||
</CollectionManagerProviderV2>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('CollectionManagerProvider', () => {
|
||||
test('should render', () => {
|
||||
const Demo = () => {
|
||||
const cm = useCollectionManagerV2();
|
||||
useEffect(() => {
|
||||
expect(cm instanceof CollectionManagerV2).toBeTruthy();
|
||||
}, [cm]);
|
||||
const users = cm.getCollection('users');
|
||||
return <div data-testid="demo">{users.name}</div>;
|
||||
};
|
||||
renderApp(Demo);
|
||||
|
||||
expect(screen.getByTestId('demo')).toHaveTextContent('users');
|
||||
});
|
||||
|
||||
test('useCollectionsV2()', () => {
|
||||
const Demo = () => {
|
||||
const collections = useCollectionsV2();
|
||||
return <div data-testid="demo">{collections.length}</div>;
|
||||
};
|
||||
renderApp(Demo);
|
||||
|
||||
expect(screen.getByTestId('demo')).toHaveTextContent('2');
|
||||
});
|
||||
|
||||
test('useCollectionsV2({ predicate })', () => {
|
||||
const Demo = () => {
|
||||
const collections = useCollectionsV2({
|
||||
predicate: (collection) => collection.name === 'users',
|
||||
});
|
||||
return <div data-testid="demo">{collections.length}</div>;
|
||||
};
|
||||
renderApp(Demo);
|
||||
|
||||
expect(screen.getByTestId('demo')).toHaveTextContent('1');
|
||||
});
|
||||
|
||||
test('useCollectionsV2({ dataSource })', () => {
|
||||
const Demo = () => {
|
||||
const collections = useCollectionsV2({
|
||||
dataSource: 'a',
|
||||
});
|
||||
return <div data-testid="demo">{collections.length}</div>;
|
||||
};
|
||||
renderApp(Demo);
|
||||
|
||||
expect(screen.getByTestId('demo')).toHaveTextContent('1');
|
||||
});
|
||||
});
|
@ -0,0 +1,89 @@
|
||||
import React, { ComponentType } from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import {
|
||||
CollectionManagerProviderV2,
|
||||
CollectionProviderV2,
|
||||
useCollectionFieldsV2,
|
||||
useCollectionV2,
|
||||
} from '../../collection';
|
||||
import { Application } from '../../Application';
|
||||
import collections from './collections.json';
|
||||
import { SchemaComponentProvider } from '../../../schema-component';
|
||||
|
||||
function renderApp(Demo: ComponentType, props: any = {}) {
|
||||
const app = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
},
|
||||
});
|
||||
return render(
|
||||
<div data-testid="app">
|
||||
<SchemaComponentProvider designable={true}>
|
||||
<CollectionManagerProviderV2 collectionManager={app.collectionManager}>
|
||||
<CollectionProviderV2 {...props}>
|
||||
<Demo></Demo>
|
||||
</CollectionProviderV2>
|
||||
</CollectionManagerProviderV2>
|
||||
</SchemaComponentProvider>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('CollectionProvider', () => {
|
||||
test('should render', () => {
|
||||
const Demo = () => {
|
||||
const collection = useCollectionV2();
|
||||
const collectionFields = useCollectionFieldsV2();
|
||||
return (
|
||||
<>
|
||||
<div data-testid="name">{collection.name}</div>
|
||||
<div data-testid="fields">{collectionFields.length}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderApp(Demo, { name: 'users' });
|
||||
|
||||
expect(screen.getByTestId('name')).toHaveTextContent('users');
|
||||
|
||||
const usersOptions = collections.find((item) => item.name === 'users');
|
||||
expect(screen.getByTestId('fields')).toHaveTextContent(String(usersOptions.fields.length));
|
||||
});
|
||||
|
||||
test('collection not exists and { allowNull: true }, should render children', () => {
|
||||
const Demo = () => {
|
||||
const collection = useCollectionV2();
|
||||
expect(collection).toBeFalsy();
|
||||
|
||||
return <div data-testid="children">children</div>;
|
||||
};
|
||||
|
||||
renderApp(Demo, { name: 'not-exists', allowNull: true });
|
||||
|
||||
expect(screen.getByTestId('children')).toHaveTextContent('children');
|
||||
});
|
||||
|
||||
test('collection not exists and { allowNull: false }, should render `DeletedPlaceholder` content', () => {
|
||||
const Demo = () => {
|
||||
const collection = useCollectionV2();
|
||||
expect(collection).toBeFalsy();
|
||||
|
||||
return <div>children</div>;
|
||||
};
|
||||
|
||||
renderApp(Demo, { name: 'not-exists', allowNull: false });
|
||||
|
||||
expect(screen.getByText(`Collection: "not-exists" not exists`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('useCollectionFieldsV2() support predicate', () => {
|
||||
const Demo = () => {
|
||||
const fields = useCollectionFieldsV2({ name: 'id' });
|
||||
return <div data-testid="fields">{fields.length}</div>;
|
||||
};
|
||||
|
||||
renderApp(Demo, { name: 'users' });
|
||||
|
||||
expect(screen.getByTestId('fields')).toHaveTextContent('1');
|
||||
});
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import { DeletedPlaceholder } from '../../collection/DeletedPlaceholder';
|
||||
import { SchemaComponentProvider } from '../../../schema-component';
|
||||
|
||||
function renderApp(name?: any, designable?: boolean) {
|
||||
render(
|
||||
<div data-testid="app">
|
||||
<SchemaComponentProvider designable={designable}>
|
||||
<DeletedPlaceholder type="test" name={name}></DeletedPlaceholder>
|
||||
</SchemaComponentProvider>
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('DeletedPlaceholder', () => {
|
||||
test('name is undefined, render nothing', () => {
|
||||
renderApp();
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('name exist', () => {
|
||||
test("designable: true & process.env.NODE_ENV === 'development', render `Result` component", () => {
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
renderApp('test', true);
|
||||
|
||||
process.env.NODE_ENV = NODE_ENV;
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML).toContain('ant-result');
|
||||
});
|
||||
|
||||
test("designable: false & process.env.NODE_ENV === 'development', render `Result` component", () => {
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
renderApp('test', false);
|
||||
|
||||
process.env.NODE_ENV = NODE_ENV;
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML).toContain('ant-result');
|
||||
});
|
||||
|
||||
test("designable: true & process.env.NODE_ENV !== 'development', render `Result` component", () => {
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
renderApp('test', true);
|
||||
|
||||
process.env.NODE_ENV = NODE_ENV;
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML).toContain('ant-result');
|
||||
});
|
||||
|
||||
test("designable: false & process.env.NODE_ENV !== 'development', render nothing", () => {
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
renderApp('test', false);
|
||||
|
||||
process.env.NODE_ENV = NODE_ENV;
|
||||
|
||||
expect(screen.getByTestId('app').innerHTML.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,14 @@
|
||||
import { RecordV2 } from '../../collection';
|
||||
|
||||
describe('Record', () => {
|
||||
test('should works', () => {
|
||||
const record = new RecordV2<{ id: number }, { name: string }>({ data: { id: 1 } });
|
||||
expect(record.data.id).toBe(1);
|
||||
|
||||
record.setData({ id: 2 });
|
||||
expect(record.data.id).toBe(2);
|
||||
|
||||
record.setParentRecord(new RecordV2({ data: { name: 'a' } }));
|
||||
expect(record.parentRecord.data.name).toBe('a');
|
||||
});
|
||||
});
|
@ -0,0 +1,200 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import {
|
||||
RecordProviderV2,
|
||||
RecordV2,
|
||||
useParentRecordDataV2,
|
||||
useParentRecordV2,
|
||||
useRecordDataV2,
|
||||
useRecordV2,
|
||||
} from '../../collection';
|
||||
|
||||
describe('RecordProvider', () => {
|
||||
describe('record and parentRecord', () => {
|
||||
test('record parameter is a `Record` instance', () => {
|
||||
const Demo = () => {
|
||||
const record = useRecordV2();
|
||||
return <pre data-testid="content">{JSON.stringify(record)}</pre>;
|
||||
};
|
||||
const record = new RecordV2({ data: { id: 1, name: 'foo' } });
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={record}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(JSON.stringify({ id: 1, name: 'foo' }));
|
||||
});
|
||||
|
||||
test('record parameter is a `plain object`', () => {
|
||||
const Demo = () => {
|
||||
const record = useRecordV2();
|
||||
return <pre data-testid="content">{JSON.stringify(record)}</pre>;
|
||||
};
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={{ id: 1, name: 'foo' }}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(JSON.stringify({ id: 1, name: 'foo' }));
|
||||
});
|
||||
|
||||
test('record parameter is a `Record` instance with parent record', () => {
|
||||
const Demo = () => {
|
||||
const record = useRecordV2();
|
||||
return <pre data-testid="content">{JSON.stringify(record)}</pre>;
|
||||
};
|
||||
|
||||
const parentRecord = new RecordV2({ data: { id: 1, role: 'admin' } });
|
||||
const record = new RecordV2({ data: { id: 1, name: 'foo' }, parentRecord });
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={record}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
},
|
||||
parentRecord: {
|
||||
data: {
|
||||
id: 1,
|
||||
role: 'admin',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('record parameter is a `Record` instance, parent record is passed through parentRecord parameter', () => {
|
||||
const Demo = () => {
|
||||
const record = useRecordV2();
|
||||
return <pre data-testid="content">{JSON.stringify(record)}</pre>;
|
||||
};
|
||||
|
||||
const parentRecord = new RecordV2({ data: { id: 1, role: 'admin' } });
|
||||
const record = new RecordV2({ data: { id: 1, name: 'foo' } });
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={record} parentRecord={parentRecord}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
},
|
||||
parentRecord: {
|
||||
data: {
|
||||
id: 1,
|
||||
role: 'admin',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('record parameter is a `plain object`, parent record is also a `plain object`', () => {
|
||||
const Demo = () => {
|
||||
const record = useRecordV2();
|
||||
return <pre data-testid="content">{JSON.stringify(record)}</pre>;
|
||||
};
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={{ id: 1, name: 'foo' }} parentRecord={{ id: 1, role: 'admin' }}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
},
|
||||
parentRecord: {
|
||||
data: {
|
||||
id: 1,
|
||||
role: 'admin',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hooks', () => {
|
||||
test('useRecordDataV2()', () => {
|
||||
const Demo = () => {
|
||||
const data = useRecordDataV2();
|
||||
return <pre data-testid="content">{JSON.stringify(data)}</pre>;
|
||||
};
|
||||
const parentRecord = new RecordV2({ data: { id: 1, role: 'admin' } });
|
||||
const record = new RecordV2({ data: { id: 1, name: 'foo' }, parentRecord });
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={record}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(JSON.stringify({ id: 1, name: 'foo' }));
|
||||
});
|
||||
|
||||
test('useParentRecordV2()', () => {
|
||||
const Demo = () => {
|
||||
const data = useParentRecordV2();
|
||||
return <pre data-testid="content">{JSON.stringify(data)}</pre>;
|
||||
};
|
||||
const parentRecord = new RecordV2({ data: { id: 1, role: 'admin' } });
|
||||
const record = new RecordV2({ data: { id: 1, name: 'foo' }, parentRecord });
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={record}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
id: 1,
|
||||
role: 'admin',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('useParentRecordDataV2()', () => {
|
||||
const Demo = () => {
|
||||
const data = useParentRecordDataV2();
|
||||
return <pre data-testid="content">{JSON.stringify(data)}</pre>;
|
||||
};
|
||||
const parentRecord = new RecordV2({ data: { id: 1, role: 'admin' } });
|
||||
const record = new RecordV2({ data: { id: 1, name: 'foo' }, parentRecord });
|
||||
|
||||
render(
|
||||
<RecordProviderV2 record={record}>
|
||||
<Demo />
|
||||
</RecordProviderV2>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent(
|
||||
JSON.stringify({
|
||||
id: 1,
|
||||
role: 'admin',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,198 @@
|
||||
[
|
||||
{
|
||||
"key": "h7b9i8khc3q",
|
||||
"name": "users",
|
||||
"inherit": false,
|
||||
"hidden": false,
|
||||
"description": null,
|
||||
"category": [],
|
||||
"namespace": "users.users",
|
||||
"duplicator": {
|
||||
"dumpable": "optional",
|
||||
"with": "rolesUsers"
|
||||
},
|
||||
"sortable": "sort",
|
||||
"model": "UserModel",
|
||||
"createdBy": true,
|
||||
"updatedBy": true,
|
||||
"logging": true,
|
||||
"from": "db2cm",
|
||||
"title": "{{t(\"Users\")}}",
|
||||
"rawTitle": "{{t(\"Users\")}}",
|
||||
"fields": [
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "number",
|
||||
"title": "{{t(\"ID\")}}",
|
||||
"x-component": "InputNumber",
|
||||
"x-read-pretty": true,
|
||||
"rawTitle": "{{t(\"ID\")}}"
|
||||
},
|
||||
"key": "ffp1f2sula0",
|
||||
"name": "id",
|
||||
"type": "bigInt",
|
||||
"interface": "id",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"autoIncrement": true,
|
||||
"primaryKey": true,
|
||||
"allowNull": false
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Nickname\")}}",
|
||||
"x-component": "Input",
|
||||
"rawTitle": "{{t(\"Nickname\")}}"
|
||||
},
|
||||
"key": "vrv7yjue90g",
|
||||
"name": "nickname",
|
||||
"type": "string",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Username\")}}",
|
||||
"x-component": "Input",
|
||||
"x-validator": {
|
||||
"username": true
|
||||
},
|
||||
"required": true,
|
||||
"rawTitle": "{{t(\"Username\")}}"
|
||||
},
|
||||
"key": "2ccs6evyrub",
|
||||
"name": "username",
|
||||
"type": "string",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Email\")}}",
|
||||
"x-component": "Input",
|
||||
"x-validator": "email",
|
||||
"required": true,
|
||||
"rawTitle": "{{t(\"Email\")}}"
|
||||
},
|
||||
"key": "rrskwjl5wt1",
|
||||
"name": "email",
|
||||
"type": "string",
|
||||
"interface": "email",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"key": "t09bauwm0wb",
|
||||
"name": "roles",
|
||||
"type": "belongsToMany",
|
||||
"interface": "m2m",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"target": "roles",
|
||||
"foreignKey": "userId",
|
||||
"otherKey": "roleName",
|
||||
"onDelete": "CASCADE",
|
||||
"sourceKey": "id",
|
||||
"targetKey": "name",
|
||||
"through": "rolesUsers",
|
||||
"uiSchema": {
|
||||
"type": "array",
|
||||
"title": "{{t(\"Roles\")}}",
|
||||
"x-component": "AssociationField",
|
||||
"x-component-props": {
|
||||
"multiple": true,
|
||||
"fieldNames": {
|
||||
"label": "title",
|
||||
"value": "name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pqnenvqrzxr",
|
||||
"name": "roles",
|
||||
"inherit": false,
|
||||
"hidden": false,
|
||||
"description": null,
|
||||
"category": [],
|
||||
"namespace": "acl.acl",
|
||||
"duplicator": {
|
||||
"dumpable": "required",
|
||||
"with": "uiSchemas"
|
||||
},
|
||||
"autoGenId": false,
|
||||
"model": "RoleModel",
|
||||
"filterTargetKey": "name",
|
||||
"sortable": true,
|
||||
"from": "db2cm",
|
||||
"title": "{{t(\"Roles\")}}",
|
||||
"rawTitle": "{{t(\"Roles\")}}",
|
||||
"fields": [
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Role UID\")}}",
|
||||
"x-component": "Input",
|
||||
"rawTitle": "{{t(\"Role UID\")}}"
|
||||
},
|
||||
"key": "jbz9m80bxmp",
|
||||
"name": "name",
|
||||
"type": "uid",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "roles",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"prefix": "r_",
|
||||
"primaryKey": true
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Role name\")}}",
|
||||
"x-component": "Input",
|
||||
"rawTitle": "{{t(\"Role name\")}}"
|
||||
},
|
||||
"key": "faywtz4sf3u",
|
||||
"name": "title",
|
||||
"type": "string",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "roles",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"unique": true,
|
||||
"translation": true
|
||||
},
|
||||
{
|
||||
"key": "1enkovm9sye",
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"interface": null,
|
||||
"description": null,
|
||||
"collectionName": "roles",
|
||||
"parentKey": null,
|
||||
"reverseKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,46 @@
|
||||
import { CollectionFieldInterfaceBase, isTitleField } from '../../collection';
|
||||
import { Application } from '../../Application';
|
||||
import collections from './collections.json';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('isTitleField', () => {
|
||||
class Demo1FieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo1';
|
||||
titleUsable = false;
|
||||
}
|
||||
class Demo2FieldInterface extends CollectionFieldInterfaceBase {
|
||||
name = 'demo2';
|
||||
titleUsable = true;
|
||||
}
|
||||
|
||||
const cm = new Application({
|
||||
collectionManager: {
|
||||
collections: collections as any,
|
||||
fieldInterfaces: [Demo1FieldInterface, Demo2FieldInterface],
|
||||
},
|
||||
}).collectionManager;
|
||||
|
||||
it('should return false when field is foreign key', () => {
|
||||
const field = {
|
||||
isForeignKey: true,
|
||||
};
|
||||
expect(isTitleField(cm, field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false when field interface is not title usable', () => {
|
||||
const field = {
|
||||
isForeignKey: false,
|
||||
interface: 'demo1',
|
||||
};
|
||||
expect(isTitleField(cm, field)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when field is not foreign key and field interface is title usable', () => {
|
||||
const field = {
|
||||
isForeignKey: false,
|
||||
interface: 'demo2',
|
||||
};
|
||||
expect(isTitleField(cm, field)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@nocobase/test/client';
|
||||
import { CollectionDataSourceProvider, useCollectionDataSourceName } from '../../data-block';
|
||||
|
||||
describe('CollectionDataSourceProvider', () => {
|
||||
test('should work', () => {
|
||||
const Demo = () => {
|
||||
const name = useCollectionDataSourceName();
|
||||
return <div data-testid="content">{name}</div>;
|
||||
};
|
||||
|
||||
render(
|
||||
<CollectionDataSourceProvider dataSource={'test'}>
|
||||
<Demo />
|
||||
</CollectionDataSourceProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('content')).toHaveTextContent('test');
|
||||
});
|
||||
});
|
@ -0,0 +1,143 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen, waitFor } from '@nocobase/test/client';
|
||||
import CollectionTableListDemo from './data-block-demos/collection-table-list';
|
||||
import CollectionFormGetAndUpdateDemo from './data-block-demos/collection-form-get-and-update';
|
||||
import CollectionFormCreateDemo from './data-block-demos/collection-form-create';
|
||||
import CollectionFormRecordAndUpdateDemo from './data-block-demos/collection-form-record-and-update';
|
||||
import AssociationTableListAndSourceIdDemo from './data-block-demos/association-table-list-and-source-id';
|
||||
import AssociationTableListAndParentRecordDemo from './data-block-demos/association-table-list-and-parent-record';
|
||||
|
||||
describe('CollectionDataSourceProvider', () => {
|
||||
describe('demos', () => {
|
||||
describe('collection', () => {
|
||||
test('Table list', async () => {
|
||||
const { getByText, getByRole } = render(<CollectionTableListDemo />);
|
||||
|
||||
// app loading
|
||||
await waitFor(() => {
|
||||
expect(getByRole('table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(getByText('UserName')).toBeInTheDocument();
|
||||
expect(getByText('NickName')).toBeInTheDocument();
|
||||
expect(getByText('Email')).toBeInTheDocument();
|
||||
|
||||
// loading table data
|
||||
await waitFor(() => {
|
||||
const columns = screen.getByRole('table').querySelectorAll('tbody tr');
|
||||
expect(columns.length).toBe(3);
|
||||
});
|
||||
|
||||
expect(getByText('jack')).toBeInTheDocument();
|
||||
expect(getByText('Jack Ma')).toBeInTheDocument();
|
||||
expect(getByText('test@gmail.com')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('Form get & update', async () => {
|
||||
render(<CollectionFormGetAndUpdateDemo />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Username')).toBeInTheDocument();
|
||||
expect(screen.getByText('Age')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// load form data success
|
||||
await waitFor(() => {
|
||||
expect(document.getElementById('username')).toHaveValue('Bamboo');
|
||||
expect(document.getElementById('age')).toHaveValue('18');
|
||||
});
|
||||
|
||||
fireEvent.click(document.querySelector('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Save successfully!')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('Form create', async () => {
|
||||
render(<CollectionFormCreateDemo />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Username')).toBeInTheDocument();
|
||||
expect(screen.getByText('Age')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.change(document.getElementById('username'), { target: { value: 'Bamboo' } });
|
||||
fireEvent.change(document.getElementById('age'), { target: { value: '18' } });
|
||||
|
||||
fireEvent.click(document.querySelector('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Save successfully!')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('Form record & update', async () => {
|
||||
render(<CollectionFormRecordAndUpdateDemo />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Username')).toBeInTheDocument();
|
||||
expect(screen.getByText('Age')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.getElementById('username')).toHaveValue('Bamboo');
|
||||
expect(document.getElementById('age')).toHaveValue('18');
|
||||
});
|
||||
|
||||
fireEvent.click(document.querySelector('button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Save successfully!')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('association', () => {
|
||||
test('Table list & sourceId', async () => {
|
||||
const { getByText, getByRole } = render(<AssociationTableListAndSourceIdDemo />);
|
||||
|
||||
// app loading
|
||||
await waitFor(() => {
|
||||
expect(getByRole('table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(getByText('Name')).toBeInTheDocument();
|
||||
expect(getByText('Title')).toBeInTheDocument();
|
||||
expect(getByText('Description')).toBeInTheDocument();
|
||||
|
||||
// loading table data
|
||||
await waitFor(() => {
|
||||
const columns = screen.getByRole('table').querySelectorAll('tbody tr');
|
||||
expect(columns.length).toBe(2);
|
||||
});
|
||||
|
||||
expect(getByText('admin')).toBeInTheDocument();
|
||||
expect(getByText('Admin')).toBeInTheDocument();
|
||||
expect(getByText('Admin description')).toBeInTheDocument();
|
||||
});
|
||||
test('Table list & parentRecord', async () => {
|
||||
const { getByText, getByRole } = render(<AssociationTableListAndParentRecordDemo />);
|
||||
|
||||
// app loading
|
||||
await waitFor(() => {
|
||||
expect(getByRole('table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(getByText('Name')).toBeInTheDocument();
|
||||
expect(getByText('Title')).toBeInTheDocument();
|
||||
expect(getByText('Description')).toBeInTheDocument();
|
||||
|
||||
// loading table data
|
||||
await waitFor(() => {
|
||||
const columns = screen.getByRole('table').querySelectorAll('tbody tr');
|
||||
expect(columns.length).toBe(2);
|
||||
});
|
||||
|
||||
expect(getByText('admin')).toBeInTheDocument();
|
||||
expect(getByText('Admin')).toBeInTheDocument();
|
||||
expect(getByText('Admin description')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,96 @@
|
||||
import React from 'react';
|
||||
import { Table, TableProps } from 'antd';
|
||||
import { SchemaComponent, UseDataBlockProps, useDataBlockRequestV2, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import { createApp } from './createApp';
|
||||
|
||||
const collection = 'users';
|
||||
const associationField = 'roles';
|
||||
|
||||
const association = `${collection}.${associationField}`;
|
||||
const action = 'list';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DataBlockProviderV2',
|
||||
'x-use-decorator-props': 'useBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
association,
|
||||
action,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
demo: {
|
||||
type: 'array',
|
||||
'x-component': 'MyTable',
|
||||
'x-use-component-props': 'useTableProps',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MyTable = withDynamicSchemaProps(Table);
|
||||
|
||||
function useTableProps(): TableProps<any> {
|
||||
const { data, loading } = useDataBlockRequestV2<any[]>();
|
||||
return {
|
||||
loading,
|
||||
dataSource: data?.data || [],
|
||||
columns: [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Title',
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'description',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const useBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
|
||||
const parentRecord = {
|
||||
id: 1,
|
||||
username: 'Tom',
|
||||
};
|
||||
return {
|
||||
parentRecord,
|
||||
};
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
return <SchemaComponent schema={schema}></SchemaComponent>;
|
||||
};
|
||||
|
||||
const mocks = {
|
||||
[`${collection}/1/${associationField}:${action}`]: {
|
||||
data: [
|
||||
{
|
||||
name: 'admin',
|
||||
title: 'Admin',
|
||||
description: 'Admin description',
|
||||
},
|
||||
{
|
||||
name: 'developer',
|
||||
title: 'Developer',
|
||||
description: 'Developer description',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const Root = createApp(
|
||||
Demo,
|
||||
{
|
||||
components: { MyTable },
|
||||
scopes: { useTableProps, useBlockDecoratorProps },
|
||||
},
|
||||
mocks,
|
||||
);
|
||||
|
||||
export default Root;
|
@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import { Select, Table, TableProps } from 'antd';
|
||||
import { SchemaComponent, UseDataBlockProps, useDataBlockRequestV2, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
|
||||
import useUrlState from '@ahooksjs/use-url-state';
|
||||
import { createApp } from './createApp';
|
||||
|
||||
const collection = 'users';
|
||||
const associationField = 'roles';
|
||||
|
||||
const association = `${collection}.${associationField}`;
|
||||
const action = 'list';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DataBlockProviderV2',
|
||||
'x-use-decorator-props': 'useBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
association,
|
||||
action,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
demo: {
|
||||
type: 'array',
|
||||
'x-component': 'MyTable',
|
||||
'x-use-component-props': 'useTableProps',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MyTable = withDynamicSchemaProps(Table);
|
||||
|
||||
function useTableProps(): TableProps<any> {
|
||||
const { data, loading } = useDataBlockRequestV2<any[]>();
|
||||
return {
|
||||
loading,
|
||||
dataSource: data?.data || [],
|
||||
columns: [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Title',
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
dataIndex: 'description',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const useBlockDecoratorProps: UseDataBlockProps<'CollectionList'> = () => {
|
||||
const [state] = useUrlState({ userId: '1' });
|
||||
return {
|
||||
sourceId: state.userId,
|
||||
};
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
const [state, setState] = useUrlState({ userId: '1' });
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
defaultValue={state.userId}
|
||||
options={[
|
||||
{ key: 1, value: '1', label: 'Tom' },
|
||||
{ key: 2, value: '2', label: 'Jack' },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setState({ userId: v });
|
||||
}}
|
||||
></Select>
|
||||
<SchemaComponent schema={schema}></SchemaComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mocks = {
|
||||
[`${collection}/1/${associationField}:${action}`]: {
|
||||
data: [
|
||||
{
|
||||
name: 'admin',
|
||||
title: 'Admin',
|
||||
description: 'Admin description',
|
||||
},
|
||||
{
|
||||
name: 'developer',
|
||||
title: 'Developer',
|
||||
description: 'Developer description',
|
||||
},
|
||||
],
|
||||
},
|
||||
[`${collection}/2/${associationField}:${action}`]: {
|
||||
data: [
|
||||
{
|
||||
name: 'developer',
|
||||
title: 'Developer',
|
||||
description: 'Developer description',
|
||||
},
|
||||
{
|
||||
name: 'tester',
|
||||
title: 'Tester',
|
||||
description: 'Tester description',
|
||||
},
|
||||
],
|
||||
},
|
||||
[`${collection}:get/1`]: {
|
||||
id: 1,
|
||||
username: 'Tom',
|
||||
},
|
||||
[`${collection}:get/2`]: {
|
||||
id: 1,
|
||||
username: 'Jack',
|
||||
},
|
||||
};
|
||||
|
||||
const Root = createApp(
|
||||
Demo,
|
||||
{
|
||||
components: { MyTable },
|
||||
scopes: { useTableProps, useBlockDecoratorProps },
|
||||
},
|
||||
mocks,
|
||||
);
|
||||
|
||||
export default Root;
|
@ -0,0 +1,94 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Button, Form, FormProps, Input, InputNumber, notification } from 'antd';
|
||||
import { SchemaComponent, useDataBlockResourceV2, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import { createApp } from './createApp';
|
||||
|
||||
interface DemoFormFieldType {
|
||||
id: number;
|
||||
username: string;
|
||||
age: number;
|
||||
}
|
||||
type DemoFormProps = FormProps<DemoFormFieldType>;
|
||||
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
|
||||
return (
|
||||
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
|
||||
<Form.Item<DemoFormFieldType>
|
||||
label="Username"
|
||||
name="username"
|
||||
rules={[{ required: true, message: 'Please input your username!' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item<DemoFormFieldType>
|
||||
label="Age"
|
||||
name="age"
|
||||
rules={[{ required: true, message: 'Please input your age!' }]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
function useDemoFormProps(): DemoFormProps {
|
||||
const resource = useDataBlockResourceV2();
|
||||
const onFinish = async (values: DemoFormFieldType) => {
|
||||
console.log('values', values);
|
||||
await resource.create({
|
||||
values,
|
||||
});
|
||||
notification.success({
|
||||
message: 'Save successfully!',
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onFinish,
|
||||
};
|
||||
}
|
||||
|
||||
const collection = 'users';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DataBlockProviderV2',
|
||||
'x-decorator-props': {
|
||||
collection: collection,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
demo: {
|
||||
type: 'object',
|
||||
'x-component': 'DemoForm',
|
||||
'x-use-component-props': 'useDemoFormProps',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
return <SchemaComponent schema={schema}></SchemaComponent>;
|
||||
};
|
||||
|
||||
const mocks = {
|
||||
[`${collection}:create`]: (config) => {
|
||||
console.log('config.data', config.data);
|
||||
return [200, { msg: 'ok' }];
|
||||
},
|
||||
};
|
||||
const Root = createApp(
|
||||
Demo,
|
||||
{
|
||||
components: { DemoForm },
|
||||
scopes: { useDemoFormProps },
|
||||
},
|
||||
mocks,
|
||||
);
|
||||
|
||||
export default Root;
|
@ -0,0 +1,158 @@
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { Button, Form, FormProps, Input, InputNumber, Select, notification } from 'antd';
|
||||
import {
|
||||
SchemaComponent,
|
||||
UseDataBlockProps,
|
||||
useDataBlockResourceV2,
|
||||
useRecordDataV2,
|
||||
withDynamicSchemaProps,
|
||||
} from '@nocobase/client';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import useUrlState from '@ahooksjs/use-url-state';
|
||||
|
||||
import { createApp } from './createApp';
|
||||
|
||||
interface DemoFormFieldType {
|
||||
id: number;
|
||||
username: string;
|
||||
age: number;
|
||||
}
|
||||
type DemoFormProps = FormProps<DemoFormFieldType>;
|
||||
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
|
||||
return (
|
||||
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
|
||||
<Form.Item<DemoFormFieldType>
|
||||
label="Username"
|
||||
name="username"
|
||||
rules={[{ required: true, message: 'Please input your username!' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item<DemoFormFieldType>
|
||||
label="Age"
|
||||
name="age"
|
||||
rules={[{ required: true, message: 'Please input your age!' }]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
function useDemoFormProps(): DemoFormProps {
|
||||
const data = useRecordDataV2<DemoFormFieldType>();
|
||||
const resource = useDataBlockResourceV2();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(data);
|
||||
}, [data, form]);
|
||||
|
||||
const onFinish = async (values: DemoFormFieldType) => {
|
||||
console.log('values', values);
|
||||
await resource.update({
|
||||
filterByTk: data.id,
|
||||
values,
|
||||
});
|
||||
notification.success({
|
||||
message: 'Save successfully!',
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
initialValues: data,
|
||||
preserve: true,
|
||||
onFinish,
|
||||
form,
|
||||
};
|
||||
}
|
||||
const useBlockDecoratorProps: UseDataBlockProps<'CollectionGet'> = () => {
|
||||
const [state] = useUrlState({ id: '1' });
|
||||
return {
|
||||
filterByTk: state.id,
|
||||
};
|
||||
};
|
||||
|
||||
const collection = 'users';
|
||||
const action = 'get';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DataBlockProviderV2',
|
||||
'x-use-decorator-props': 'useBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
collection: collection,
|
||||
action: action,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
demo: {
|
||||
type: 'object',
|
||||
'x-component': 'DemoForm',
|
||||
'x-use-component-props': 'useDemoFormProps',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
const [state, setState] = useUrlState({ id: '1' });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
defaultValue={state.id}
|
||||
options={[
|
||||
{ key: 1, value: '1', label: 'Bamboo' },
|
||||
{ key: 2, value: '2', label: 'Mary' },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setState({ id: v });
|
||||
}}
|
||||
></Select>
|
||||
<SchemaComponent schema={schema}></SchemaComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mocks = {
|
||||
[`${collection}:${action}`]: function (config) {
|
||||
const { filterByTk } = config.params;
|
||||
return {
|
||||
data:
|
||||
Number(filterByTk) === 1
|
||||
? {
|
||||
id: 1,
|
||||
username: 'Bamboo',
|
||||
age: 18,
|
||||
}
|
||||
: {
|
||||
id: 2,
|
||||
username: 'Mary',
|
||||
age: 25,
|
||||
},
|
||||
};
|
||||
},
|
||||
[`${collection}:update`]: function (config) {
|
||||
console.log('config.data', config.data);
|
||||
return {
|
||||
data: 'ok',
|
||||
};
|
||||
},
|
||||
};
|
||||
const Root = createApp(
|
||||
Demo,
|
||||
{
|
||||
components: { DemoForm },
|
||||
scopes: { useDemoFormProps, useBlockDecoratorProps },
|
||||
},
|
||||
mocks,
|
||||
);
|
||||
|
||||
export default Root;
|
@ -0,0 +1,136 @@
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { Button, Form, FormProps, Input, InputNumber, notification } from 'antd';
|
||||
import {
|
||||
RecordProviderV2,
|
||||
SchemaComponent,
|
||||
UseDataBlockProps,
|
||||
useDataBlockResourceV2,
|
||||
useRecordDataV2,
|
||||
withDynamicSchemaProps,
|
||||
} from '@nocobase/client';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import { createApp } from './createApp';
|
||||
|
||||
interface DemoFormFieldType {
|
||||
id: number;
|
||||
username: string;
|
||||
age: number;
|
||||
}
|
||||
type DemoFormProps = FormProps<DemoFormFieldType>;
|
||||
const DemoForm: FC<DemoFormProps> = withDynamicSchemaProps((props) => {
|
||||
return (
|
||||
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }} autoComplete="off" {...props}>
|
||||
<Form.Item<DemoFormFieldType>
|
||||
label="Username"
|
||||
name="username"
|
||||
rules={[{ required: true, message: 'Please input your username!' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item<DemoFormFieldType>
|
||||
label="Age"
|
||||
name="age"
|
||||
rules={[{ required: true, message: 'Please input your age!' }]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
function useDemoFormProps(): DemoFormProps {
|
||||
const data = useRecordDataV2<DemoFormFieldType>();
|
||||
const resource = useDataBlockResourceV2();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(data);
|
||||
}, [data, form]);
|
||||
|
||||
const onFinish = async (values: DemoFormFieldType) => {
|
||||
console.log('values', values);
|
||||
await resource.update({
|
||||
filterByTk: data.id,
|
||||
values,
|
||||
});
|
||||
notification.success({
|
||||
message: 'Save successfully!',
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
initialValues: data,
|
||||
preserve: true,
|
||||
onFinish,
|
||||
form,
|
||||
};
|
||||
}
|
||||
const useFormBlockDecoratorProps: UseDataBlockProps<'CollectionRecord'> = () => {
|
||||
const record = useRecordDataV2();
|
||||
return {
|
||||
record,
|
||||
};
|
||||
};
|
||||
|
||||
const collection = 'users';
|
||||
const action = 'get';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DataBlockProviderV2',
|
||||
'x-use-decorator-props': 'useFormBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
collection: collection,
|
||||
action: action,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
demo: {
|
||||
type: 'object',
|
||||
'x-component': 'DemoForm',
|
||||
'x-use-component-props': 'useDemoFormProps',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const recordData = {
|
||||
id: 1,
|
||||
username: 'Bamboo',
|
||||
age: 18,
|
||||
};
|
||||
|
||||
const Demo = () => {
|
||||
return (
|
||||
<>
|
||||
<RecordProviderV2 record={recordData}>
|
||||
<SchemaComponent schema={schema}></SchemaComponent>
|
||||
</RecordProviderV2>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mocks = {
|
||||
[`${collection}:update`]: function (config) {
|
||||
console.log('config.data', config.data);
|
||||
return {
|
||||
data: 'ok',
|
||||
};
|
||||
},
|
||||
};
|
||||
const Root = createApp(
|
||||
Demo,
|
||||
{
|
||||
components: { DemoForm },
|
||||
scopes: { useDemoFormProps, useFormBlockDecoratorProps },
|
||||
},
|
||||
mocks,
|
||||
);
|
||||
|
||||
export default Root;
|
@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { SchemaComponent, useDataBlockRequestV2, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { Table, TableProps } from 'antd';
|
||||
import { ISchema } from '@formily/json-schema';
|
||||
import { createApp } from './createApp';
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'root',
|
||||
'x-decorator': 'DataBlockProviderV2',
|
||||
'x-decorator-props': {
|
||||
collection: 'users',
|
||||
action: 'list',
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
properties: {
|
||||
demo: {
|
||||
type: 'array',
|
||||
'x-component': 'MyTable',
|
||||
'x-use-component-props': 'useTableProps', // 动态 table 属性
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const MyTable = withDynamicSchemaProps(Table);
|
||||
|
||||
function useTableProps(): TableProps<any> {
|
||||
const { data, loading } = useDataBlockRequestV2<any[]>();
|
||||
return {
|
||||
loading,
|
||||
dataSource: data?.data || [],
|
||||
columns: [
|
||||
{
|
||||
title: 'UserName',
|
||||
dataIndex: 'username',
|
||||
},
|
||||
{
|
||||
title: 'NickName',
|
||||
dataIndex: 'nickname',
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const Demo = () => {
|
||||
return <SchemaComponent schema={schema}></SchemaComponent>;
|
||||
};
|
||||
|
||||
const mocks = {
|
||||
'users:list': {
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
username: 'jack',
|
||||
nickname: 'Jack Ma',
|
||||
email: 'test@gmail.com',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
username: 'jim',
|
||||
nickname: 'Jim Green',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
username: 'tom',
|
||||
nickname: 'Tom Cat',
|
||||
email: 'tom@gmail.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
'roles:list': {
|
||||
data: [
|
||||
{
|
||||
name: 'root',
|
||||
title: 'Root',
|
||||
description: 'Root',
|
||||
},
|
||||
{
|
||||
name: 'admin',
|
||||
title: 'Admin',
|
||||
description: 'Admin description',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const App = createApp(
|
||||
Demo,
|
||||
{
|
||||
components: { MyTable },
|
||||
scopes: { useTableProps },
|
||||
},
|
||||
mocks,
|
||||
);
|
||||
|
||||
export default App;
|
@ -0,0 +1,198 @@
|
||||
[
|
||||
{
|
||||
"key": "h7b9i8khc3q",
|
||||
"name": "users",
|
||||
"inherit": false,
|
||||
"hidden": false,
|
||||
"description": null,
|
||||
"category": [],
|
||||
"namespace": "users.users",
|
||||
"duplicator": {
|
||||
"dumpable": "optional",
|
||||
"with": "rolesUsers"
|
||||
},
|
||||
"sortable": "sort",
|
||||
"model": "UserModel",
|
||||
"createdBy": true,
|
||||
"updatedBy": true,
|
||||
"logging": true,
|
||||
"from": "db2cm",
|
||||
"title": "{{t(\"Users\")}}",
|
||||
"rawTitle": "{{t(\"Users\")}}",
|
||||
"fields": [
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "number",
|
||||
"title": "{{t(\"ID\")}}",
|
||||
"x-component": "InputNumber",
|
||||
"x-read-pretty": true,
|
||||
"rawTitle": "{{t(\"ID\")}}"
|
||||
},
|
||||
"key": "ffp1f2sula0",
|
||||
"name": "id",
|
||||
"type": "bigInt",
|
||||
"interface": "id",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"autoIncrement": true,
|
||||
"primaryKey": true,
|
||||
"allowNull": false
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Nickname\")}}",
|
||||
"x-component": "Input",
|
||||
"rawTitle": "{{t(\"Nickname\")}}"
|
||||
},
|
||||
"key": "vrv7yjue90g",
|
||||
"name": "nickname",
|
||||
"type": "string",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Username\")}}",
|
||||
"x-component": "Input",
|
||||
"x-validator": {
|
||||
"username": true
|
||||
},
|
||||
"required": true,
|
||||
"rawTitle": "{{t(\"Username\")}}"
|
||||
},
|
||||
"key": "2ccs6evyrub",
|
||||
"name": "username",
|
||||
"type": "string",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Email\")}}",
|
||||
"x-component": "Input",
|
||||
"x-validator": "email",
|
||||
"required": true,
|
||||
"rawTitle": "{{t(\"Email\")}}"
|
||||
},
|
||||
"key": "rrskwjl5wt1",
|
||||
"name": "email",
|
||||
"type": "string",
|
||||
"interface": "email",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"unique": true
|
||||
},
|
||||
{
|
||||
"key": "t09bauwm0wb",
|
||||
"name": "roles",
|
||||
"type": "belongsToMany",
|
||||
"interface": "m2m",
|
||||
"description": null,
|
||||
"collectionName": "users",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"target": "roles",
|
||||
"foreignKey": "userId",
|
||||
"otherKey": "roleName",
|
||||
"onDelete": "CASCADE",
|
||||
"sourceKey": "id",
|
||||
"targetKey": "name",
|
||||
"through": "rolesUsers",
|
||||
"uiSchema": {
|
||||
"type": "array",
|
||||
"title": "{{t(\"Roles\")}}",
|
||||
"x-component": "AssociationField",
|
||||
"x-component-props": {
|
||||
"multiple": true,
|
||||
"fieldNames": {
|
||||
"label": "title",
|
||||
"value": "name"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "pqnenvqrzxr",
|
||||
"name": "roles",
|
||||
"inherit": false,
|
||||
"hidden": false,
|
||||
"description": null,
|
||||
"category": [],
|
||||
"namespace": "acl.acl",
|
||||
"duplicator": {
|
||||
"dumpable": "required",
|
||||
"with": "uiSchemas"
|
||||
},
|
||||
"autoGenId": false,
|
||||
"model": "RoleModel",
|
||||
"filterTargetKey": "name",
|
||||
"sortable": true,
|
||||
"from": "db2cm",
|
||||
"title": "{{t(\"Roles\")}}",
|
||||
"rawTitle": "{{t(\"Roles\")}}",
|
||||
"fields": [
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Role UID\")}}",
|
||||
"x-component": "Input",
|
||||
"rawTitle": "{{t(\"Role UID\")}}"
|
||||
},
|
||||
"key": "jbz9m80bxmp",
|
||||
"name": "name",
|
||||
"type": "uid",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "roles",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"prefix": "r_",
|
||||
"primaryKey": true
|
||||
},
|
||||
{
|
||||
"uiSchema": {
|
||||
"type": "string",
|
||||
"title": "{{t(\"Role name\")}}",
|
||||
"x-component": "Input",
|
||||
"rawTitle": "{{t(\"Role name\")}}"
|
||||
},
|
||||
"key": "faywtz4sf3u",
|
||||
"name": "title",
|
||||
"type": "string",
|
||||
"interface": "input",
|
||||
"description": null,
|
||||
"collectionName": "roles",
|
||||
"parentKey": null,
|
||||
"reverseKey": null,
|
||||
"unique": true,
|
||||
"translation": true
|
||||
},
|
||||
{
|
||||
"key": "1enkovm9sye",
|
||||
"name": "description",
|
||||
"type": "string",
|
||||
"interface": null,
|
||||
"description": null,
|
||||
"collectionName": "roles",
|
||||
"parentKey": null,
|
||||
"reverseKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,49 @@
|
||||
import {
|
||||
ApplicationOptions,
|
||||
Application,
|
||||
CardItem,
|
||||
CollectionManagerOptionsV2,
|
||||
CollectionPlugin,
|
||||
DataBlockProviderV2,
|
||||
} from '@nocobase/client';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { ComponentType } from 'react';
|
||||
import collections from './collections.json';
|
||||
|
||||
export function createApp(Demo: ComponentType<any>, options: ApplicationOptions = {}, mocks: Record<string, any>) {
|
||||
const collectionManager = {
|
||||
collections: collections as any,
|
||||
...(options.collectionManager as CollectionManagerOptionsV2),
|
||||
};
|
||||
const app = new Application({
|
||||
apiClient: {
|
||||
baseURL: 'http://localhost:8000',
|
||||
},
|
||||
providers: [Demo],
|
||||
...options,
|
||||
collectionManager,
|
||||
components: {
|
||||
...options.components,
|
||||
DataBlockProviderV2,
|
||||
CardItem,
|
||||
},
|
||||
plugins: [CollectionPlugin],
|
||||
designable: true,
|
||||
});
|
||||
|
||||
const mock = new MockAdapter(app.apiClient.axios);
|
||||
|
||||
Object.entries(mocks).forEach(([url, data]) => {
|
||||
mock.onGet(url).reply(async (config) => {
|
||||
const res = typeof data === 'function' ? data(config) : data;
|
||||
return [200, res];
|
||||
});
|
||||
mock.onPost(url).reply(async (config) => {
|
||||
const res = typeof data === 'function' ? data(config) : data;
|
||||
return [200, res];
|
||||
});
|
||||
});
|
||||
|
||||
const Root = app.getRootComponent();
|
||||
return Root;
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import { SchemaComponent, SchemaComponentProvider } from '../../../schema-component';
|
||||
import { render, screen, sleep, userEvent, waitFor } from '@nocobase/test/client';
|
||||
import { withDynamicSchemaProps } from '../../hoc';
|
||||
|
||||
const HelloComponent = withDynamicSchemaProps((props: any) => (
|
||||
<pre data-testid="component">{JSON.stringify(props)}</pre>
|
||||
));
|
||||
const HelloDecorator = withDynamicSchemaProps(({ children, ...others }) => (
|
||||
<div>
|
||||
<pre data-testid="decorator">{JSON.stringify(others)}</pre>
|
||||
{children}
|
||||
</div>
|
||||
));
|
||||
|
||||
function withTestDemo(schema: any, scopes?: any) {
|
||||
const Demo = () => {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ HelloComponent, HelloDecorator }} scope={scopes}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'hello',
|
||||
'x-component': 'HelloComponent',
|
||||
'x-decorator': 'HelloDecorator',
|
||||
...schema,
|
||||
}}
|
||||
/>
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
return Demo;
|
||||
}
|
||||
|
||||
describe('withDynamicSchemaProps', () => {
|
||||
test('x-use-component-props', () => {
|
||||
function useComponentProps() {
|
||||
return {
|
||||
a: 'a',
|
||||
};
|
||||
}
|
||||
const schema = {
|
||||
'x-use-component-props': 'useComponentProps',
|
||||
};
|
||||
const scopes = { useComponentProps };
|
||||
|
||||
const Demo = withTestDemo(schema, scopes);
|
||||
const { getByTestId } = render(<Demo />);
|
||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ a: 'a' }));
|
||||
});
|
||||
|
||||
test('x-use-component-props and x-component-props should merge', () => {
|
||||
function useComponentProps() {
|
||||
return {
|
||||
a: 'a',
|
||||
};
|
||||
}
|
||||
const schema = {
|
||||
'x-use-component-props': 'useComponentProps',
|
||||
'x-component-props': {
|
||||
b: 'b',
|
||||
},
|
||||
};
|
||||
const scopes = { useComponentProps };
|
||||
|
||||
const Demo = withTestDemo(schema, scopes);
|
||||
const { getByTestId } = render(<Demo />);
|
||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ a: 'a', b: 'b' }));
|
||||
});
|
||||
|
||||
test('x-use-decorator-props', () => {
|
||||
function useDecoratorProps() {
|
||||
return {
|
||||
a: 'a',
|
||||
};
|
||||
}
|
||||
const schema = {
|
||||
'x-use-decorator-props': 'useDecoratorProps',
|
||||
};
|
||||
const scopes = { useDecoratorProps };
|
||||
|
||||
const Demo = withTestDemo(schema, scopes);
|
||||
const { getByTestId } = render(<Demo />);
|
||||
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ a: 'a' }));
|
||||
});
|
||||
|
||||
test('x-use-decorator-props and x-decorator-props should merge', () => {
|
||||
function useDecoratorProps() {
|
||||
return {
|
||||
a: 'a',
|
||||
};
|
||||
}
|
||||
const schema = {
|
||||
'x-use-decorator-props': 'useDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
b: 'b',
|
||||
},
|
||||
};
|
||||
const scopes = { useDecoratorProps };
|
||||
|
||||
const Demo = withTestDemo(schema, scopes);
|
||||
const { getByTestId } = render(<Demo />);
|
||||
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ a: 'a', b: 'b' }));
|
||||
});
|
||||
|
||||
test('x-use-component-props and x-use-decorator-props exist simultaneously', () => {
|
||||
function useDecoratorProps() {
|
||||
return {
|
||||
a: 'a',
|
||||
};
|
||||
}
|
||||
function useComponentProps() {
|
||||
return {
|
||||
c: 'c',
|
||||
};
|
||||
}
|
||||
const schema = {
|
||||
'x-use-decorator-props': 'useDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
b: 'b',
|
||||
},
|
||||
'x-use-component-props': 'useComponentProps',
|
||||
'x-component-props': {
|
||||
d: 'd',
|
||||
},
|
||||
};
|
||||
|
||||
const scopes = { useDecoratorProps, useComponentProps };
|
||||
|
||||
const Demo = withTestDemo(schema, scopes);
|
||||
const { getByTestId } = render(<Demo />);
|
||||
expect(getByTestId('decorator')).toHaveTextContent(JSON.stringify({ a: 'a', b: 'b' }));
|
||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({ c: 'c', d: 'd' }));
|
||||
});
|
||||
|
||||
test('no register scope', () => {
|
||||
const schema = {
|
||||
'x-use-component-props': 'useComponentProps',
|
||||
};
|
||||
const Demo = withTestDemo(schema);
|
||||
const { getByTestId } = render(<Demo />);
|
||||
expect(getByTestId('component')).toHaveTextContent(JSON.stringify({}));
|
||||
});
|
||||
});
|
@ -20,7 +20,7 @@ export const AssociationProviderV2: FC<AssociationProviderProps> = (props) => {
|
||||
if (!collectionName) return <DeletedPlaceholder type="Collection" name={collectionName} />;
|
||||
|
||||
return (
|
||||
<CollectionProviderV2 name={name.split('.')[0]} dataSource={dataSource}>
|
||||
<CollectionProviderV2 name={String(name).split('.')[0]} dataSource={dataSource}>
|
||||
<CollectionFieldProviderV2 name={name}>
|
||||
<CollectionProviderV2 name={collectionName} dataSource={dataSource}>
|
||||
{children}
|
||||
|
@ -90,7 +90,7 @@ export class CollectionV2 {
|
||||
this.options = options;
|
||||
}
|
||||
get fields() {
|
||||
return this.options?.fields || [];
|
||||
return this.options.fields || [];
|
||||
}
|
||||
|
||||
get dataSource() {
|
||||
@ -113,9 +113,39 @@ export class CollectionV2 {
|
||||
get inherit() {
|
||||
return this.options.inherit;
|
||||
}
|
||||
get hidden() {
|
||||
return this.options.hidden;
|
||||
}
|
||||
get description() {
|
||||
return this.options.description;
|
||||
}
|
||||
get duplicator() {
|
||||
return this.options.duplicator;
|
||||
}
|
||||
get category() {
|
||||
return this.options.category;
|
||||
}
|
||||
get targetKey() {
|
||||
return this.options.targetKey;
|
||||
}
|
||||
get model() {
|
||||
return this.options.model;
|
||||
}
|
||||
get createdBy() {
|
||||
return this.options.createdBy;
|
||||
}
|
||||
get updatedBy() {
|
||||
return this.options.updatedBy;
|
||||
}
|
||||
get logging() {
|
||||
return this.options.logging;
|
||||
}
|
||||
get from() {
|
||||
return this.options.from;
|
||||
}
|
||||
get rawTitle() {
|
||||
return this.options.rawTitle;
|
||||
}
|
||||
get isLocal() {
|
||||
return this.options.isLocal;
|
||||
}
|
||||
@ -126,7 +156,7 @@ export class CollectionV2 {
|
||||
if (this.options.targetKey) {
|
||||
return this.options.targetKey;
|
||||
}
|
||||
const field = this.getFields('primaryKey')[0];
|
||||
const field = this.getFields({ primaryKey: true })[0];
|
||||
this.primaryKey = field ? field.name : 'id';
|
||||
|
||||
return this.primaryKey;
|
||||
@ -137,7 +167,7 @@ export class CollectionV2 {
|
||||
}
|
||||
|
||||
get titleField() {
|
||||
return this.hasField(this.options.titleField) ? this.options.titleField : this.primaryKey;
|
||||
return this.hasField(this.options.titleField) ? this.options.titleField : this.getPrimaryKey();
|
||||
}
|
||||
|
||||
get sources() {
|
||||
@ -220,6 +250,7 @@ export class CollectionV2 {
|
||||
return this.fieldsMap;
|
||||
}
|
||||
getField(name: SchemaKey) {
|
||||
if (!name) return undefined;
|
||||
const fieldsMap = this.getFieldsMap();
|
||||
if (typeof name === 'string' && name.startsWith(`${this.name}.`)) {
|
||||
name = name.replace(`${this.name}.`, '');
|
||||
@ -227,13 +258,13 @@ export class CollectionV2 {
|
||||
if (String(name).split('.').length > 1) {
|
||||
const [fieldName, ...others] = String(name).split('.');
|
||||
const field = fieldsMap[fieldName];
|
||||
if (!field) return null;
|
||||
if (!field) return undefined;
|
||||
|
||||
const collectionName = field?.target;
|
||||
if (!collectionName) return null;
|
||||
if (!collectionName) return undefined;
|
||||
|
||||
const collection = this.collectionManager.getCollection(collectionName);
|
||||
if (!collection) return null;
|
||||
if (!collection) return undefined;
|
||||
|
||||
return collection.getField(others.join('.'));
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ CollectionFieldContextV2.displayName = 'CollectionFieldContextV2';
|
||||
export type CollectionFieldProviderProps = {
|
||||
name?: SchemaKey;
|
||||
children?: ReactNode;
|
||||
fallback?: React.ReactElement;
|
||||
};
|
||||
|
||||
export const CollectionFieldProviderV2: FC<CollectionFieldProviderProps> = (props) => {
|
||||
|
@ -22,7 +22,7 @@ function applyMixins(instance: any, mixins: any[]) {
|
||||
}
|
||||
|
||||
const defaultCollectionTransform = (collection: CollectionOptionsV2, app: Application) => {
|
||||
const { rawTitle, title, fields, ...rest } = collection;
|
||||
const { rawTitle, title, fields = [], ...rest } = collection;
|
||||
return {
|
||||
...rest,
|
||||
title: rawTitle ? title : app.i18n.t(title),
|
||||
@ -68,10 +68,11 @@ export interface CollectionManagerOptionsV2 {
|
||||
fieldInterfaces?: (typeof CollectionFieldInterfaceBase)[];
|
||||
fieldGroups?: Record<string, { label: string; order?: number }>;
|
||||
collectionMixins?: CollectionMixinConstructor[];
|
||||
dataSources?: DataSource[];
|
||||
}
|
||||
|
||||
type ThirdDataResourceFn = () => Promise<DataSource[]>;
|
||||
type MainDataSOurceFn = () => Promise<CollectionOptionsV2[]>;
|
||||
type MainDataSourceFn = () => Promise<CollectionOptionsV2[]>;
|
||||
type ReloadCallback = (collections: CollectionOptionsV2[]) => void;
|
||||
|
||||
export class CollectionManagerV2 {
|
||||
@ -80,12 +81,17 @@ export class CollectionManagerV2 {
|
||||
protected collectionTemplateInstances: Record<string, CollectionTemplateBase> = {};
|
||||
protected fieldInterfaceInstances: Record<string, CollectionFieldInterfaceBase> = {};
|
||||
protected collectionMixins: CollectionMixinConstructor[] = [];
|
||||
protected dataSourceMap: Record<DataSourceNameType, Omit<DataSource, 'collections'>> = {};
|
||||
protected dataSourceMap: Record<DataSourceNameType, Omit<DataSource, 'collections'>> = {
|
||||
[DEFAULT_DATA_SOURCE_NAME]: {
|
||||
name: DEFAULT_DATA_SOURCE_NAME,
|
||||
description: DEFAULT_DATA_SOURCE_TITLE,
|
||||
},
|
||||
};
|
||||
protected collectionFieldGroups: Record<string, { label: string; order?: number }> = {};
|
||||
protected mainDataSourceFn: MainDataSOurceFn;
|
||||
protected mainDataSourceFn: MainDataSourceFn;
|
||||
protected thirdDataSourceFn: ThirdDataResourceFn;
|
||||
protected reloadCallbacks: Record<string, ReloadCallback[]> = {};
|
||||
protected collectionArr: Record<string, CollectionV2[]> = {};
|
||||
protected collectionCachedArr: Record<string, CollectionV2[]> = {};
|
||||
protected options: CollectionManagerOptionsV2 = {};
|
||||
|
||||
constructor(options: CollectionManagerOptionsV2 = {}, app: Application) {
|
||||
@ -95,7 +101,7 @@ export class CollectionManagerV2 {
|
||||
}
|
||||
|
||||
private init(options: CollectionManagerOptionsV2) {
|
||||
this.initDataSourceMap();
|
||||
this.addDataSources(options.dataSources || []);
|
||||
this.collectionMixins.push(...(options.collectionMixins || []));
|
||||
this.addCollectionTemplates(options.collectionTemplates || []);
|
||||
this.addFieldInterfaces(options.fieldInterfaces || []);
|
||||
@ -103,28 +109,19 @@ export class CollectionManagerV2 {
|
||||
this.addCollections(options.collections || []);
|
||||
}
|
||||
|
||||
private initDataSourceMap() {
|
||||
this.dataSourceMap = {
|
||||
[DEFAULT_DATA_SOURCE_NAME]: {
|
||||
name: DEFAULT_DATA_SOURCE_NAME,
|
||||
description: DEFAULT_DATA_SOURCE_TITLE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// collection mixins
|
||||
addCollectionMixins(mixins: CollectionMixinConstructor[]) {
|
||||
if (mixins.length === 0) return;
|
||||
addCollectionMixins(mixins: CollectionMixinConstructor[] = []) {
|
||||
const newMixins = mixins.filter((mixin) => !this.collectionMixins.includes(mixin));
|
||||
this.collectionMixins.push(...newMixins);
|
||||
|
||||
// 重新添加数据表
|
||||
// Re-add tables
|
||||
Object.keys(this.collections).forEach((dataSource) => {
|
||||
const collections = this.getCollections({ dataSource }).map((item) => item.getOptions());
|
||||
this.addCollections(collections, { dataSource });
|
||||
});
|
||||
}
|
||||
|
||||
// collections
|
||||
protected getCollectionInstance(collection: CollectionOptionsV2, dataSource?: string) {
|
||||
const collectionTemplateInstance = this.getCollectionTemplate(collection.template);
|
||||
const Cls = collectionTemplateInstance?.Collection || CollectionV2;
|
||||
@ -135,10 +132,9 @@ export class CollectionManagerV2 {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// collections
|
||||
addCollections(collections: CollectionOptionsV2[], options: GetCollectionOptions = {}) {
|
||||
addCollections(collections: CollectionOptionsV2[] = [], options: GetCollectionOptions = {}) {
|
||||
const { dataSource = DEFAULT_DATA_SOURCE_NAME } = options;
|
||||
this.collectionArr[dataSource] = undefined;
|
||||
this.collectionCachedArr[dataSource] = undefined;
|
||||
|
||||
collections
|
||||
.map((collection) => {
|
||||
@ -173,23 +169,23 @@ export class CollectionManagerV2 {
|
||||
}
|
||||
getCollections(options: { predicate?: (collection: CollectionV2) => boolean; dataSource?: string } = {}) {
|
||||
const { dataSource = DEFAULT_DATA_SOURCE_NAME, predicate } = options;
|
||||
if (!this.collectionArr[dataSource]?.length) {
|
||||
this.collectionArr[dataSource] = Object.values(this.collections[dataSource] || {});
|
||||
if (!this.collectionCachedArr[dataSource]?.length) {
|
||||
this.collectionCachedArr[dataSource] = Object.values(this.collections[dataSource] || {});
|
||||
}
|
||||
if (predicate) {
|
||||
return this.collectionArr[dataSource].filter(predicate);
|
||||
return this.collectionCachedArr[dataSource].filter(predicate);
|
||||
}
|
||||
return this.collectionArr[dataSource];
|
||||
return this.collectionCachedArr[dataSource];
|
||||
}
|
||||
/**
|
||||
* 获取数据表
|
||||
* Get a collection
|
||||
* @example
|
||||
* getCollection('users'); // 获取 users 表
|
||||
* getCollection('users.profile'); // 获取 users 表的 profile 字段的关联表
|
||||
* getCollection('a.b.c'); // 获取 a 表的 b 字段的关联表,然后 b.target 表对应的 c 字段的关联表
|
||||
* getCollection('users'); // Get the 'users' collection
|
||||
* getCollection('users.profile'); // Get the associated collection of the 'profile' field in the 'users' collection
|
||||
* getCollection('a.b.c'); // Get the associated collection of the 'c' field in the 'a' collection, which is associated with the 'b' field in the 'a' collection
|
||||
*/
|
||||
getCollection<Mixins = {}>(
|
||||
path: string | CollectionOptionsV2,
|
||||
path: SchemaKey | CollectionOptionsV2,
|
||||
options: GetCollectionOptions = {},
|
||||
): (Mixins & CollectionV2) | undefined {
|
||||
if (typeof path === 'object') {
|
||||
@ -197,11 +193,10 @@ export class CollectionManagerV2 {
|
||||
}
|
||||
|
||||
const { dataSource = DEFAULT_DATA_SOURCE_NAME } = options;
|
||||
if (!path || typeof path !== 'string') return undefined;
|
||||
if (path.split('.').length > 1) {
|
||||
// 获取到关联字段
|
||||
if (!path) return undefined;
|
||||
if (String(path).split('.').length > 1) {
|
||||
const associationField = this.getCollectionField(path);
|
||||
|
||||
if (!associationField) return undefined;
|
||||
return this.getCollection(associationField.target, { dataSource });
|
||||
}
|
||||
return this.collections[dataSource]?.[path] as Mixins & CollectionV2;
|
||||
@ -215,19 +210,25 @@ export class CollectionManagerV2 {
|
||||
return this.getCollection(collectionName, options)?.getFields() || [];
|
||||
}
|
||||
/**
|
||||
* 获取数据表字段
|
||||
* Get collection fields
|
||||
* @example
|
||||
* getCollection('users.username'); // 获取 users 表的 username 字段
|
||||
* getCollection('a.b.c'); // 获取 a 表的 b 字段的关联表,然后 b.target 表对应的 c 字段
|
||||
* getCollection('users.username'); // Get the 'username' field of the 'users' collection
|
||||
* getCollection('a.b.c'); // Get the associated collection of the 'c' field in the 'a' collection, which is associated with the 'b' field in the 'a' collection
|
||||
*/
|
||||
getCollectionField(path: SchemaKey, options: GetCollectionOptions = {}): CollectionFieldOptionsV2 | undefined {
|
||||
getCollectionField(
|
||||
path: SchemaKey | object,
|
||||
options: GetCollectionOptions = {},
|
||||
): CollectionFieldOptionsV2 | undefined {
|
||||
if (!path) return;
|
||||
if (typeof path === 'object' || String(path).split('.').length < 2) {
|
||||
if (typeof path === 'object') {
|
||||
return path;
|
||||
}
|
||||
if (String(path).split('.').length < 2) {
|
||||
console.error(`[@nocobase/client]: CollectionManager.getCollectionField() path "${path}" is invalid`);
|
||||
return;
|
||||
}
|
||||
const [collectionName, ...fieldNames] = String(path).split('.');
|
||||
const { dataSource = DEFAULT_DATA_SOURCE_NAME } = options || {};
|
||||
const { dataSource = DEFAULT_DATA_SOURCE_NAME } = options;
|
||||
const collection = this.getCollection(collectionName, { dataSource });
|
||||
if (!collection) {
|
||||
return;
|
||||
@ -304,7 +305,7 @@ export class CollectionManagerV2 {
|
||||
return this.collectionFieldGroups[name];
|
||||
}
|
||||
|
||||
setMainDataSource(fn: MainDataSOurceFn) {
|
||||
setMainDataSource(fn: MainDataSourceFn) {
|
||||
this.mainDataSourceFn = fn;
|
||||
}
|
||||
|
||||
@ -313,26 +314,35 @@ export class CollectionManagerV2 {
|
||||
}
|
||||
|
||||
async reloadMain(callback?: ReloadCallback) {
|
||||
if (!this.mainDataSourceFn) return;
|
||||
const collections = await this.mainDataSourceFn();
|
||||
this.setCollections(collections);
|
||||
callback && callback(collections);
|
||||
this.reloadCallbacks[DEFAULT_DATA_SOURCE_NAME]?.forEach((cb) => cb(collections));
|
||||
}
|
||||
|
||||
async reloadThirdDataSource(callback?: () => void) {
|
||||
if (!this.thirdDataSourceFn) return;
|
||||
const data = await this.thirdDataSourceFn();
|
||||
this.initDataSourceMap();
|
||||
data.forEach((dataSource) => {
|
||||
const { collections: _unUse, ...rest } = dataSource;
|
||||
this.dataSourceMap[dataSource.name] = rest;
|
||||
});
|
||||
|
||||
data.forEach(({ name, collections, ...others }) => {
|
||||
addDataSources(dataSources: DataSource[] = []) {
|
||||
dataSources.forEach(({ name, collections, ...others }) => {
|
||||
this.dataSourceMap[name] = { ...others, name };
|
||||
this.setCollections(collections, { dataSource: name });
|
||||
this.reloadCallbacks[name]?.forEach((cb) => cb(collections));
|
||||
});
|
||||
}
|
||||
|
||||
private initDataSource() {
|
||||
this.dataSourceMap = {
|
||||
[DEFAULT_DATA_SOURCE_NAME]: {
|
||||
name: DEFAULT_DATA_SOURCE_NAME,
|
||||
description: DEFAULT_DATA_SOURCE_TITLE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async reloadThirdDataSource(callback?: () => void) {
|
||||
if (!this.thirdDataSourceFn) return;
|
||||
const data = await this.thirdDataSourceFn();
|
||||
this.initDataSource();
|
||||
this.addDataSources([...(this.options.dataSources || []), ...data]);
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
@ -361,7 +371,7 @@ export class CollectionManagerV2 {
|
||||
mainDataSourceFn: this.mainDataSourceFn,
|
||||
thirdDataSourceFn: this.thirdDataSourceFn,
|
||||
reloadCallbacks: this.reloadCallbacks,
|
||||
collectionArr: this.collectionArr,
|
||||
collectionCachedArr: this.collectionCachedArr,
|
||||
options: this.options,
|
||||
};
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export function useCollectionManagerV2() {
|
||||
|
||||
export const useCollectionsV2 = (options?: {
|
||||
predicate?: (collection: CollectionV2) => boolean;
|
||||
dataSources?: string;
|
||||
dataSource?: string;
|
||||
}) => {
|
||||
const collectionManager = useCollectionManagerV2();
|
||||
const collections = useMemo(() => collectionManager.getCollections(options), [collectionManager, options]);
|
||||
|
@ -47,7 +47,9 @@ export class CollectionTemplateBase {
|
||||
}
|
||||
name: string;
|
||||
Collection?: typeof CollectionV2;
|
||||
transform?: (collection: CollectionOptionsV2, app: Application) => CollectionOptionsV2;
|
||||
transform(collection: CollectionOptionsV2, app: Application): CollectionOptionsV2 {
|
||||
return collection;
|
||||
}
|
||||
title?: string;
|
||||
color?: string;
|
||||
/** 排序 */
|
||||
|
@ -10,6 +10,10 @@ export const DeletedPlaceholder: FC<{ type: string; name?: string | number }> =
|
||||
console.error(`DeletedPlaceholder: ${type} name is required`);
|
||||
return null;
|
||||
}
|
||||
if (!designable && process.env.NODE_ENV !== 'development') return null;
|
||||
return <Result status="warning" title={t(`${type}: "${name}" has been deleted`)} />;
|
||||
|
||||
if (designable || process.env.NODE_ENV === 'development') {
|
||||
return <Result status="warning" title={t(`${type}: "${name}" not exists`)} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
@ -1,107 +1,10 @@
|
||||
import { CascaderProps } from 'antd';
|
||||
// import { CascaderProps } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import type { CollectionManagerV2 } from './CollectionManager';
|
||||
import { CollectionFieldOptionsV2 } from './Collection';
|
||||
|
||||
export function getCollectionFieldsOptions(
|
||||
collectionName: string,
|
||||
type: string | string[] = 'string',
|
||||
opts: {
|
||||
collectionManager: CollectionManagerV2;
|
||||
compile: (str: string) => string;
|
||||
cached?: Record<string, any>;
|
||||
collectionNames?: string[];
|
||||
/**
|
||||
* 为 true 时允许查询所有关联字段
|
||||
* 为 Array<string> 时仅允许查询指定的关联字段
|
||||
*/
|
||||
association?: boolean | string[];
|
||||
/**
|
||||
* Max depth of recursion
|
||||
*/
|
||||
maxDepth?: number;
|
||||
allowAllTypes?: boolean;
|
||||
/**
|
||||
* 排除这些接口的字段
|
||||
*/
|
||||
exceptInterfaces?: string[];
|
||||
/**
|
||||
* field value 的前缀,用 . 连接,比如 a.b.c
|
||||
*/
|
||||
prefixFieldValue?: string;
|
||||
/**
|
||||
* 是否使用 prefixFieldValue 作为 field value
|
||||
*/
|
||||
usePrefix?: boolean;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
association = false,
|
||||
cached = {},
|
||||
collectionNames = [collectionName],
|
||||
maxDepth = 1,
|
||||
allowAllTypes = false,
|
||||
exceptInterfaces = [],
|
||||
prefixFieldValue = '',
|
||||
compile,
|
||||
collectionManager,
|
||||
usePrefix = false,
|
||||
} = opts || {};
|
||||
|
||||
if (collectionNames.length - 1 > maxDepth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cached[collectionName]) {
|
||||
// avoid infinite recursion
|
||||
return _.cloneDeep(cached[collectionName]);
|
||||
}
|
||||
|
||||
if (typeof type === 'string') {
|
||||
type = [type];
|
||||
}
|
||||
const fields = collectionManager.getCollectionFields(collectionName);
|
||||
const options = fields
|
||||
?.filter(
|
||||
(field) =>
|
||||
field.interface &&
|
||||
!exceptInterfaces.includes(field.interface) &&
|
||||
(allowAllTypes ||
|
||||
type.includes(field.type) ||
|
||||
(association && field.target && field.target !== collectionName && Array.isArray(association)
|
||||
? association.includes(field.interface)
|
||||
: false)),
|
||||
)
|
||||
?.map((field) => {
|
||||
const result: CascaderProps<any>['options'][0] = {
|
||||
value: usePrefix && prefixFieldValue ? `${prefixFieldValue}.${field.name}` : field.name,
|
||||
label: compile(field?.uiSchema?.title) || field.name,
|
||||
...field,
|
||||
};
|
||||
if (association && field.target) {
|
||||
result.children = collectionNames.includes(field.target)
|
||||
? []
|
||||
: getCollectionFieldsOptions(field.target, type, {
|
||||
...opts,
|
||||
cached,
|
||||
collectionManager,
|
||||
compile,
|
||||
collectionNames: [...collectionNames, field.target],
|
||||
prefixFieldValue: usePrefix ? (prefixFieldValue ? `${prefixFieldValue}.${field.name}` : field.name) : '',
|
||||
usePrefix,
|
||||
});
|
||||
if (!result.children?.length) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
// 过滤 map 产生为 null 的数据
|
||||
.filter(Boolean);
|
||||
|
||||
cached[collectionName] = options;
|
||||
return options;
|
||||
}
|
||||
// 等把老的去掉后,再把这个函数的实现从那边移动过来
|
||||
// export function getCollectionFieldsOptions(){}
|
||||
|
||||
export const isTitleField = (cm: CollectionManagerV2, field: CollectionFieldOptionsV2) => {
|
||||
return !field.isForeignKey && cm.getFieldInterface(field.interface)?.titleUsable;
|
||||
|
@ -3,7 +3,7 @@ import React, { FC, ReactNode, createContext, useContext } from 'react';
|
||||
export const CollectionDataSourceName = createContext<string>(undefined);
|
||||
CollectionDataSourceName.displayName = 'CollectionDataSourceName';
|
||||
|
||||
export const CollectionDataSourceProvider: FC<{ dataSource: string; children: ReactNode }> = ({
|
||||
export const CollectionDataSourceProvider: FC<{ dataSource: string; children?: ReactNode }> = ({
|
||||
dataSource,
|
||||
children,
|
||||
}) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useDeepCompareEffect } from 'ahooks';
|
||||
import { useDeepCompareEffect, useUpdateEffect } from 'ahooks';
|
||||
import React, { FC, createContext, useContext, useEffect } from 'react';
|
||||
|
||||
import { UseRequestResult, useAPIClient, useRequest } from '../../api-client';
|
||||
@ -40,7 +40,7 @@ function useCurrentRequest<T>(options: Omit<AllDataBlockProps, 'type'>) {
|
||||
}
|
||||
}, [params, action, record]);
|
||||
|
||||
useEffect(() => {
|
||||
useUpdateEffect(() => {
|
||||
if (action) {
|
||||
request.run();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SchemaKey } from '@formily/react';
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { useCollectionV2 } from '../../application';
|
||||
import { useCollectionV2 } from '../../application/collection/CollectionProvider';
|
||||
import { InheritanceCollectionMixin } from '../mixins/InheritanceCollectionMixin';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
@ -11,9 +11,9 @@ export const useCollection = () => {
|
||||
const api = useAPIClient();
|
||||
const resource = api?.resource(collection?.name);
|
||||
const currentFields = useMemo(() => collection?.fields || [], [collection]);
|
||||
const inheritedFields = useMemo(() => collection?.getInheritedFields() || [], [collection]);
|
||||
const totalFields = useMemo(() => collection?.getAllFields() || [], [collection]);
|
||||
const foreignKeyFields = useMemo(() => collection?.getForeignKeyFields() || [], [collection]);
|
||||
const inheritedFields = useMemo(() => collection?.getInheritedFields?.() || [], [collection]);
|
||||
const totalFields = useMemo(() => collection?.getAllFields?.() || collection?.getFields() || [], [collection]);
|
||||
const foreignKeyFields = useMemo(() => collection?.getForeignKeyFields?.() || [], [collection]);
|
||||
const getTreeParentField = useCallback(() => totalFields?.find((field) => field.treeParent), [totalFields]);
|
||||
const getField = useCallback(
|
||||
(name: SchemaKey) => {
|
||||
|
@ -3,7 +3,7 @@ import _ from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCompile, useSchemaComponentContext } from '../../schema-component';
|
||||
import { CollectionFieldOptions, CollectionOptions } from '../types';
|
||||
import { useCollectionManagerV2 } from '../../application';
|
||||
import { useCollectionManagerV2 } from '../../application/collection/CollectionManagerProvider';
|
||||
import { InheritanceCollectionMixin } from '../mixins/InheritanceCollectionMixin';
|
||||
import { uid } from '@formily/shared';
|
||||
import { useCollectionDataSourceName } from '../../application/data-block';
|
||||
@ -56,13 +56,11 @@ export const useCollectionManager = (dataSourceName?: string) => {
|
||||
const getCollectionFields = useCallback(
|
||||
(name: any, customDataSource?: string): CollectionFieldOptions[] => {
|
||||
if (!name) return [];
|
||||
return (
|
||||
cm
|
||||
?.getCollection<InheritanceCollectionMixin>(name, {
|
||||
dataSource: customDataSource || dataSourceNameValue,
|
||||
})
|
||||
?.getAllFields() || []
|
||||
);
|
||||
const collection = cm?.getCollection<InheritanceCollectionMixin>(name, {
|
||||
dataSource: customDataSource || dataSourceNameValue,
|
||||
});
|
||||
|
||||
return collection?.getAllFields?.() || collection?.getFields() || [];
|
||||
},
|
||||
[cm],
|
||||
);
|
||||
|
@ -40,6 +40,7 @@ import { Router } from 'react-router-dom';
|
||||
import {
|
||||
APIClientProvider,
|
||||
ActionContextProvider,
|
||||
CollectionDataSourceProvider,
|
||||
CollectionFieldOptions,
|
||||
CollectionManagerProviderV2,
|
||||
CollectionProvider,
|
||||
@ -57,6 +58,7 @@ import {
|
||||
useActionContext,
|
||||
useBlockRequestContext,
|
||||
useCollection,
|
||||
useCollectionDataSourceName,
|
||||
useCollectionManager,
|
||||
useCollectionManagerV2,
|
||||
useCompile,
|
||||
@ -961,6 +963,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
const ctx = useContext(BlockRequestContext);
|
||||
const upLevelActiveFields = useFormActiveFields();
|
||||
const { locale } = useContext(ConfigProvider.ConfigContext);
|
||||
const dataSourceName = useCollectionDataSourceName();
|
||||
|
||||
if (hidden) {
|
||||
return null;
|
||||
@ -979,32 +982,34 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
<FormActiveFieldsProvider name="form" getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}>
|
||||
<Router location={location} navigator={null}>
|
||||
<BlockRequestContext.Provider value={ctx}>
|
||||
<CollectionManagerProviderV2 collectionManager={cm}>
|
||||
<CollectionProvider allowNull name={collection.name}>
|
||||
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
||||
<FormLayout
|
||||
layout={'vertical'}
|
||||
className={css`
|
||||
// screen > 576px
|
||||
@media (min-width: 576px) {
|
||||
min-width: 520px;
|
||||
}
|
||||
<CollectionDataSourceProvider dataSource={dataSourceName}>
|
||||
<CollectionManagerProviderV2 collectionManager={cm}>
|
||||
<CollectionProvider allowNull name={collection.name}>
|
||||
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
||||
<FormLayout
|
||||
layout={'vertical'}
|
||||
className={css`
|
||||
// screen > 576px
|
||||
@media (min-width: 576px) {
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
// screen <= 576px
|
||||
@media (max-width: 576px) {
|
||||
min-width: 320px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<APIClientProvider apiClient={apiClient}>
|
||||
<ConfigProvider locale={locale}>
|
||||
<SchemaComponent components={components} scope={scope} schema={schema} />
|
||||
</ConfigProvider>
|
||||
</APIClientProvider>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</CollectionProvider>
|
||||
</CollectionManagerProviderV2>
|
||||
// screen <= 576px
|
||||
@media (max-width: 576px) {
|
||||
min-width: 320px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<APIClientProvider apiClient={apiClient}>
|
||||
<ConfigProvider locale={locale}>
|
||||
<SchemaComponent components={components} scope={scope} schema={schema} />
|
||||
</ConfigProvider>
|
||||
</APIClientProvider>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</CollectionProvider>
|
||||
</CollectionManagerProviderV2>
|
||||
</CollectionDataSourceProvider>
|
||||
</BlockRequestContext.Provider>
|
||||
</Router>
|
||||
</FormActiveFieldsProvider>
|
||||
|
Loading…
Reference in New Issue
Block a user