* test: add tests for lazy loading of association fields * refactor: migrate * test: add tests for page * test: add tests for page menu * test: add tests for tabs * test: add tests for detail block * test: add tests for list block * test: add tests for grid card block * test: add tests for filter collapse block * test: add tests for markdown block * test: add tests for table block * test: add tests for table block * test: add tests for lazy loading of association fields * test: add tests for data scope * test: add tests for filter block * test: add tests for block template * test: add tests for drag and sorting * test: add tests for sorting rules * test: make testing more stable * Revert "test: make testing more stable" This reverts commit 78b7badeb65bfc603fb746efbdb8e242a763ae6a. * perf: remove enableToConfig * test: make testing more stable * test: make testing more stable * test: delete newly created records to make tests more stable * fix: fix error when deleting records * test: make testing more stable * test: make testing more stable * test: fix tests * refactor: optimize file structure * test: fix tests * test: fix tests * refactor: optimize description * refactor: optimize description * refactor: use __e2e__ as the root directory for test files * fix: fix build * test: make testing more stable
310 lines
10 KiB
TypeScript
310 lines
10 KiB
TypeScript
import Database, { Collection, Repository } from '@nocobase/database';
|
|
import { CollectionRepository } from '@nocobase/plugin-collection-manager';
|
|
import { Plugin } from '@nocobase/server';
|
|
import { merge, uid } from '@nocobase/utils';
|
|
import _ from 'lodash';
|
|
import collectionTemplates from './collection-templates';
|
|
import * as fieldInterfaces from './field-interfaces';
|
|
import { mockAttachment } from './field-interfaces';
|
|
|
|
export class PluginMockCollectionsServer extends Plugin {
|
|
async load() {
|
|
const templates = collectionTemplates;
|
|
|
|
const defaultFields = {
|
|
id: () => ({
|
|
name: 'id',
|
|
type: 'bigInt',
|
|
autoIncrement: true,
|
|
primaryKey: true,
|
|
allowNull: false,
|
|
uiSchema: { type: 'number', title: '{{t("ID")}}', 'x-component': 'InputNumber', 'x-read-pretty': true },
|
|
interface: 'id',
|
|
}),
|
|
createdAt: () => ({
|
|
name: 'createdAt',
|
|
interface: 'createdAt',
|
|
type: 'date',
|
|
field: 'createdAt',
|
|
uiSchema: {
|
|
type: 'datetime',
|
|
title: '{{t("Created at")}}',
|
|
'x-component': 'DatePicker',
|
|
'x-component-props': {},
|
|
'x-read-pretty': true,
|
|
},
|
|
}),
|
|
createdBy: () => ({
|
|
name: 'createdBy',
|
|
interface: 'createdBy',
|
|
type: 'belongsTo',
|
|
target: 'users',
|
|
foreignKey: 'createdById',
|
|
uiSchema: {
|
|
type: 'object',
|
|
title: '{{t("Created by")}}',
|
|
'x-component': 'AssociationField',
|
|
'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } },
|
|
'x-read-pretty': true,
|
|
},
|
|
}),
|
|
updatedAt: () => ({
|
|
type: 'date',
|
|
field: 'updatedAt',
|
|
name: 'updatedAt',
|
|
interface: 'updatedAt',
|
|
uiSchema: {
|
|
type: 'string',
|
|
title: '{{t("Last updated at")}}',
|
|
'x-component': 'DatePicker',
|
|
'x-component-props': {},
|
|
'x-read-pretty': true,
|
|
},
|
|
}),
|
|
updatedBy: () => ({
|
|
type: 'belongsTo',
|
|
target: 'users',
|
|
foreignKey: 'updatedById',
|
|
name: 'updatedBy',
|
|
interface: 'updatedBy',
|
|
uiSchema: {
|
|
type: 'object',
|
|
title: '{{t("Last updated by")}}',
|
|
'x-component': 'AssociationField',
|
|
'x-component-props': { fieldNames: { value: 'id', label: 'nickname' } },
|
|
'x-read-pretty': true,
|
|
},
|
|
}),
|
|
};
|
|
|
|
const mockCollection = (values) => {
|
|
if (values.autoCreate || values.view) {
|
|
if (!values.key) {
|
|
values.key = uid();
|
|
}
|
|
if (Array.isArray(values.fields)) {
|
|
values.fields = values.fields.map((f) => {
|
|
if (!f.key) {
|
|
f.key = uid();
|
|
}
|
|
return f;
|
|
});
|
|
}
|
|
return values;
|
|
}
|
|
const defaults = {
|
|
logging: true,
|
|
autoGenId: true,
|
|
createdBy: true,
|
|
updatedBy: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
sortable: true,
|
|
template: 'general',
|
|
view: false,
|
|
sort: 1,
|
|
name: `t_${uid()}`,
|
|
...values,
|
|
};
|
|
defaults.key = uid();
|
|
if (!defaults.title) {
|
|
defaults.title = defaults.name;
|
|
}
|
|
if (!defaults.fields?.length) {
|
|
defaults.fields = [];
|
|
}
|
|
const fieldNames = defaults.fields.map((f) => f.name);
|
|
const fn = templates[defaults.template] || templates.general;
|
|
const { fields = [], ...others } = fn(defaults);
|
|
Object.assign(defaults, others);
|
|
for (const field of fields) {
|
|
if (!fieldNames.includes(field.name)) {
|
|
defaults.fields.push(field);
|
|
}
|
|
}
|
|
if (['general', 'file', 'tree', 'calendar', 'expression'].includes(defaults.template)) {
|
|
if (defaults.autoGenId && !fieldNames.includes('id')) {
|
|
defaults.fields.push(defaultFields.id());
|
|
}
|
|
if (defaults.createdAt && !fieldNames.includes('createdAt')) {
|
|
defaults.fields.push(defaultFields.createdAt());
|
|
}
|
|
if (defaults.updatedAt && !fieldNames.includes('updatedAt')) {
|
|
defaults.fields.push(defaultFields.updatedAt());
|
|
}
|
|
if (defaults.createdBy && !fieldNames.includes('createdBy')) {
|
|
defaults.fields.push(defaultFields.createdBy());
|
|
}
|
|
if (defaults.updatedBy && !fieldNames.includes('updatedBy')) {
|
|
defaults.fields.push(defaultFields.updatedBy());
|
|
}
|
|
const foreignKeyFields = [];
|
|
defaults.fields = defaults.fields.map((field) => {
|
|
field.collectionName = defaults.name;
|
|
const f = mockCollectionField(field);
|
|
if (f.foreignKeyFields) {
|
|
foreignKeyFields.push(...f.foreignKeyFields);
|
|
}
|
|
if (f.reverseField) {
|
|
foreignKeyFields.push(f.reverseField);
|
|
}
|
|
if (f.throughCollection) {
|
|
defaults['throughCollection'] = f.throughCollection;
|
|
}
|
|
f.collectionName = defaults.name;
|
|
return _.omit(f, ['reverseField', 'throughCollection', 'foreignKeyFields']);
|
|
});
|
|
defaults.fields.push(...foreignKeyFields);
|
|
}
|
|
return defaults;
|
|
};
|
|
|
|
const mockCollectionField = (opts) => {
|
|
let options = opts;
|
|
options.sort = 1;
|
|
if (options.interface) {
|
|
const fn = fieldInterfaces[options.interface];
|
|
if (fn) {
|
|
const defaultValue = fn.options(options);
|
|
options = merge(defaultValue, options);
|
|
}
|
|
}
|
|
if (!options.key) {
|
|
options.key = uid();
|
|
}
|
|
if (!options.name) {
|
|
options.name = `f_${uid()}`;
|
|
}
|
|
if (options.title && options.uiSchema) {
|
|
options.uiSchema.title = options.title;
|
|
}
|
|
if (options.uiSchema && !options.uiSchema.title) {
|
|
options.uiSchema.title = options.name;
|
|
}
|
|
return options;
|
|
};
|
|
|
|
this.app.resourcer.registerActions({
|
|
mock: async (ctx, next) => {
|
|
const { resourceName } = ctx.action;
|
|
const { values, count = 10 } = ctx.action.params;
|
|
const mockCollectionData = async (collectionName, count = 1, depth = 0, maxDepth = 4) => {
|
|
const collection = ctx.db.getCollection(collectionName) as Collection;
|
|
const items = await Promise.all(
|
|
_.range(count).map(async (i) => {
|
|
if (collection.options.template === 'file') {
|
|
return mockAttachment();
|
|
}
|
|
const v = {};
|
|
if (collection.options.sortable) {
|
|
v['sort'] = i + 1;
|
|
}
|
|
for (const field of collection.fields.values()) {
|
|
if (!field.options.interface) {
|
|
continue;
|
|
}
|
|
if (depth >= maxDepth && ['m2o', 'm2m', 'o2m', 'obo', 'oho'].includes(field.options.interface)) {
|
|
continue;
|
|
}
|
|
const fn = fieldInterfaces[field.options.interface];
|
|
if (fn?.mock) {
|
|
v[field.name] = await fn.mock(field.options, { mockCollectionData, maxDepth, depth });
|
|
}
|
|
}
|
|
return v;
|
|
}),
|
|
);
|
|
return count == 1 ? items[0] : items;
|
|
};
|
|
const repository = ctx.db.getRepository(resourceName);
|
|
let size = count;
|
|
if (Array.isArray(values)) {
|
|
size = values.length;
|
|
}
|
|
const data = await mockCollectionData(resourceName, size);
|
|
// ctx.body = {
|
|
// values: (Array.isArray(data) ? data : [data]).map((item, index) => {
|
|
// if (Array.isArray(values)) {
|
|
// return { ...item, ...values[index] };
|
|
// }
|
|
// return { ...item, ...values };
|
|
// }),
|
|
// };
|
|
ctx.body = await repository.create({
|
|
values: (Array.isArray(data) ? data : [data]).map((item, index) => {
|
|
if (Array.isArray(values)) {
|
|
return { ...item, ...values[index] };
|
|
}
|
|
return { ...item, ...values };
|
|
}),
|
|
});
|
|
await next();
|
|
},
|
|
'collections:mock': async (ctx, next) => {
|
|
const { values } = ctx.action.params;
|
|
const items = Array.isArray(values) ? values : [values || {}];
|
|
const collections = [];
|
|
const collectionFields = [];
|
|
for (const item of items) {
|
|
const { fields = [], throughCollection, ...opts } = mockCollection(item);
|
|
collections.push(opts);
|
|
if (throughCollection) {
|
|
collections.push(throughCollection);
|
|
}
|
|
collectionFields.push(...fields);
|
|
}
|
|
// ctx.body = {
|
|
// collections,
|
|
// collectionFields,
|
|
// };
|
|
// await next();
|
|
// return;
|
|
const db = ctx.db as Database;
|
|
const collectionsRepository = db.getRepository('collections') as CollectionRepository;
|
|
const fieldsRepository = db.getRepository('fields') as Repository;
|
|
await db.sequelize.transaction(async (transaction) => {
|
|
for (const collection of collections) {
|
|
try {
|
|
await collectionsRepository.firstOrCreate({
|
|
values: collection,
|
|
filterKeys: ['name'],
|
|
hooks: false,
|
|
transaction,
|
|
});
|
|
} catch (error) {
|
|
this.app.log.error(error.message, { collection });
|
|
throw error;
|
|
}
|
|
}
|
|
for (const collectionField of collectionFields) {
|
|
try {
|
|
await fieldsRepository.firstOrCreate({
|
|
values: collectionField,
|
|
filterKeys: ['name', 'collectionName'],
|
|
hooks: false,
|
|
transaction,
|
|
});
|
|
} catch (error) {
|
|
this.app.log.error(error.message, { collectionField });
|
|
throw error;
|
|
}
|
|
}
|
|
});
|
|
|
|
await collectionsRepository.load();
|
|
await db.sync();
|
|
const records = await collectionsRepository.find({
|
|
filter: {
|
|
name: collections.map((c) => c.name),
|
|
},
|
|
appends: ['fields'],
|
|
});
|
|
ctx.body = Array.isArray(values) ? records : records[0];
|
|
await next();
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
export default PluginMockCollectionsServer;
|