tachybase_todo/packages/plugins/@nocobase/plugin-mock-collections/src/server/index.ts
Zeke Zhang 3b7c1345cc
test(e2e): add tests for client (#3144)
* 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
2023-12-13 14:14:33 +08:00

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;