@nocobase/test 0.21.0-alpha.1 → 0.21.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,19 @@
1
- /// <reference types="react" />
1
+ import React, { FC } from 'react';
2
+ import { GetAppComponentOptions, GetAppOptions } from '../web';
2
3
  export { renderHook } from '@testing-library/react-hooks';
3
4
  declare function customRender(ui: React.ReactElement, options?: {}): import("@testing-library/react").RenderResult<typeof import("@testing-library/dom/types/queries"), HTMLElement, HTMLElement>;
4
5
  export * from '@testing-library/react';
5
6
  export { default as userEvent } from '@testing-library/user-event';
6
7
  export { customRender as render };
7
8
  export declare const sleep: (timeout?: number) => Promise<unknown>;
9
+ export declare const WaitApp: () => Promise<void>;
10
+ interface RenderHookOptions extends Omit<GetAppOptions, 'value' | 'onChange'> {
11
+ hook: () => any;
12
+ props?: any;
13
+ Wrapper?: FC<{
14
+ children: React.ReactNode;
15
+ }>;
16
+ }
17
+ export declare const renderHookWithApp: (options: RenderHookOptions) => Promise<import("@testing-library/react-hooks").RenderHookResult<any, any, import("@testing-library/react-hooks").Renderer<any>>>;
18
+ export declare const renderApp: (options: GetAppComponentOptions) => Promise<import("@testing-library/react").RenderResult<typeof import("@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>;
19
+ export declare const renderReadPrettyApp: (options: GetAppComponentOptions) => Promise<import("@testing-library/react").RenderResult<typeof import("@testing-library/dom/types/queries"), HTMLElement, HTMLElement>>;
@@ -1,7 +1,504 @@
1
- import { render } from "@testing-library/react";
1
+ import { expect } from "vitest";
2
+ import React, { Fragment } from "react";
3
+ import { render, waitFor, screen } from "@testing-library/react";
2
4
  export * from "@testing-library/react";
3
5
  import { renderHook } from "@testing-library/react-hooks";
6
+ import { renderHook as renderHook2 } from "@testing-library/react-hooks";
7
+ import MockAdapter from "axios-mock-adapter";
8
+ import { Application, LocalDataSource, SchemaComponent, DataBlockProvider } from "@nocobase/client";
4
9
  import { default as default2 } from "@testing-library/user-event";
10
+ const dataSourceMainCollections = [
11
+ {
12
+ key: "h7b9i8khc3q",
13
+ name: "users",
14
+ inherit: false,
15
+ hidden: false,
16
+ description: null,
17
+ category: [],
18
+ namespace: "users.users",
19
+ duplicator: {
20
+ dumpable: "optional",
21
+ "with": "rolesUsers"
22
+ },
23
+ sortable: "sort",
24
+ model: "UserModel",
25
+ createdBy: true,
26
+ updatedBy: true,
27
+ logging: true,
28
+ from: "db2cm",
29
+ title: '{{t("Users")}}',
30
+ rawTitle: '{{t("Users")}}',
31
+ fields: [
32
+ {
33
+ uiSchema: {
34
+ type: "number",
35
+ title: '{{t("ID")}}',
36
+ "x-component": "InputNumber",
37
+ "x-read-pretty": true,
38
+ rawTitle: '{{t("ID")}}'
39
+ },
40
+ key: "ffp1f2sula0",
41
+ name: "id",
42
+ type: "bigInt",
43
+ "interface": "id",
44
+ description: null,
45
+ collectionName: "users",
46
+ parentKey: null,
47
+ reverseKey: null,
48
+ autoIncrement: true,
49
+ primaryKey: true,
50
+ allowNull: false
51
+ },
52
+ {
53
+ uiSchema: {
54
+ type: "string",
55
+ title: '{{t("Nickname")}}',
56
+ "x-component": "Input",
57
+ rawTitle: '{{t("Nickname")}}'
58
+ },
59
+ key: "vrv7yjue90g",
60
+ name: "nickname",
61
+ type: "string",
62
+ "interface": "input",
63
+ description: null,
64
+ collectionName: "users",
65
+ parentKey: null,
66
+ reverseKey: null
67
+ },
68
+ {
69
+ uiSchema: {
70
+ type: "string",
71
+ title: '{{t("Username")}}',
72
+ "x-component": "Input",
73
+ "x-validator": {
74
+ username: true
75
+ },
76
+ required: true,
77
+ rawTitle: '{{t("Username")}}'
78
+ },
79
+ key: "2ccs6evyrub",
80
+ name: "username",
81
+ type: "string",
82
+ "interface": "input",
83
+ description: null,
84
+ collectionName: "users",
85
+ parentKey: null,
86
+ reverseKey: null,
87
+ unique: true
88
+ },
89
+ {
90
+ uiSchema: {
91
+ type: "string",
92
+ title: '{{t("Email")}}',
93
+ "x-component": "Input",
94
+ "x-validator": "email",
95
+ required: true,
96
+ rawTitle: '{{t("Email")}}'
97
+ },
98
+ key: "rrskwjl5wt1",
99
+ name: "email",
100
+ type: "string",
101
+ "interface": "email",
102
+ description: null,
103
+ collectionName: "users",
104
+ parentKey: null,
105
+ reverseKey: null,
106
+ unique: true
107
+ },
108
+ {
109
+ key: "t09bauwm0wb",
110
+ name: "roles",
111
+ type: "belongsToMany",
112
+ "interface": "m2m",
113
+ description: null,
114
+ collectionName: "users",
115
+ parentKey: null,
116
+ reverseKey: null,
117
+ target: "roles",
118
+ foreignKey: "userId",
119
+ otherKey: "roleName",
120
+ onDelete: "CASCADE",
121
+ sourceKey: "id",
122
+ targetKey: "name",
123
+ through: "rolesUsers",
124
+ uiSchema: {
125
+ type: "array",
126
+ title: '{{t("Roles")}}',
127
+ "x-component": "AssociationField",
128
+ "x-component-props": {
129
+ multiple: true,
130
+ fieldNames: {
131
+ label: "title",
132
+ value: "name"
133
+ }
134
+ }
135
+ }
136
+ },
137
+ {
138
+ key: "1pz0art9mt7",
139
+ name: "f_n2fu6hvprct",
140
+ type: "string",
141
+ "interface": "select",
142
+ description: null,
143
+ collectionName: "t_vwpds9fs4xs",
144
+ parentKey: null,
145
+ reverseKey: null,
146
+ uiSchema: {
147
+ "enum": [
148
+ {
149
+ value: "test1",
150
+ label: "test1"
151
+ },
152
+ {
153
+ value: "test2",
154
+ label: "test2"
155
+ }
156
+ ],
157
+ type: "string",
158
+ "x-component": "Select",
159
+ title: "test"
160
+ }
161
+ }
162
+ ]
163
+ },
164
+ {
165
+ key: "pqnenvqrzxr",
166
+ name: "roles",
167
+ inherit: false,
168
+ hidden: false,
169
+ description: null,
170
+ category: [],
171
+ namespace: "acl.acl",
172
+ duplicator: {
173
+ dumpable: "required",
174
+ "with": "uiSchemas"
175
+ },
176
+ autoGenId: false,
177
+ model: "RoleModel",
178
+ filterTargetKey: "name",
179
+ sortable: true,
180
+ from: "db2cm",
181
+ title: '{{t("Roles")}}',
182
+ rawTitle: '{{t("Roles")}}',
183
+ fields: [
184
+ {
185
+ uiSchema: {
186
+ type: "string",
187
+ title: '{{t("Role UID")}}',
188
+ "x-component": "Input",
189
+ rawTitle: '{{t("Role UID")}}'
190
+ },
191
+ key: "jbz9m80bxmp",
192
+ name: "name",
193
+ type: "uid",
194
+ "interface": "input",
195
+ description: null,
196
+ collectionName: "roles",
197
+ parentKey: null,
198
+ reverseKey: null,
199
+ prefix: "r_",
200
+ primaryKey: true
201
+ },
202
+ {
203
+ uiSchema: {
204
+ type: "string",
205
+ title: '{{t("Role name")}}',
206
+ "x-component": "Input",
207
+ rawTitle: '{{t("Role name")}}'
208
+ },
209
+ key: "faywtz4sf3u",
210
+ name: "title",
211
+ type: "string",
212
+ "interface": "input",
213
+ description: null,
214
+ collectionName: "roles",
215
+ parentKey: null,
216
+ reverseKey: null,
217
+ unique: true,
218
+ translation: true
219
+ },
220
+ {
221
+ key: "1enkovm9sye",
222
+ name: "description",
223
+ type: "string",
224
+ "interface": null,
225
+ description: null,
226
+ collectionName: "roles",
227
+ parentKey: null,
228
+ reverseKey: null
229
+ }
230
+ ]
231
+ }
232
+ ];
233
+ const key = "data-source2";
234
+ const displayName = "Data Source 2";
235
+ const status = "loaded";
236
+ const type = "postgres";
237
+ const isDBInstance = true;
238
+ const collections = [
239
+ {
240
+ name: "test",
241
+ title: "test",
242
+ tableName: "test",
243
+ timestamps: false,
244
+ autoGenId: false,
245
+ filterTargetKey: "id",
246
+ fields: [
247
+ {
248
+ name: "id",
249
+ type: "integer",
250
+ allowNull: false,
251
+ primaryKey: false,
252
+ unique: false,
253
+ autoIncrement: true,
254
+ possibleTypes: [
255
+ "integer",
256
+ "sort"
257
+ ],
258
+ rawType: "INTEGER",
259
+ "interface": "integer",
260
+ uiSchema: {
261
+ type: "number",
262
+ "x-component": "InputNumber",
263
+ "x-component-props": {
264
+ stringMode: true,
265
+ step: "1"
266
+ },
267
+ "x-validator": "integer",
268
+ title: "id"
269
+ }
270
+ },
271
+ {
272
+ name: "title",
273
+ type: "string",
274
+ allowNull: false,
275
+ primaryKey: false,
276
+ unique: false,
277
+ possibleTypes: [
278
+ "string",
279
+ "uuid",
280
+ "nanoid"
281
+ ],
282
+ rawType: "CHARACTER VARYING(255)",
283
+ "interface": "input",
284
+ uiSchema: {
285
+ "x-component": "Input",
286
+ "x-component-props": {
287
+ style: {
288
+ width: "100%"
289
+ }
290
+ },
291
+ title: "title"
292
+ }
293
+ },
294
+ {
295
+ name: "content",
296
+ type: "text",
297
+ allowNull: true,
298
+ primaryKey: false,
299
+ unique: false,
300
+ rawType: "TEXT",
301
+ "interface": "textarea",
302
+ uiSchema: {
303
+ type: "string",
304
+ "x-component": "Input.TextArea",
305
+ title: "content"
306
+ }
307
+ }
308
+ ],
309
+ introspected: true
310
+ },
311
+ {
312
+ name: "test2",
313
+ title: "test2",
314
+ tableName: "test2",
315
+ timestamps: false,
316
+ autoGenId: false,
317
+ filterTargetKey: "id",
318
+ fields: [
319
+ {
320
+ name: "id",
321
+ type: "integer",
322
+ allowNull: false,
323
+ primaryKey: true,
324
+ unique: false,
325
+ autoIncrement: true,
326
+ possibleTypes: [
327
+ "integer",
328
+ "sort"
329
+ ],
330
+ rawType: "INTEGER",
331
+ "interface": "integer",
332
+ uiSchema: {
333
+ type: "number",
334
+ "x-component": "InputNumber",
335
+ "x-component-props": {
336
+ stringMode: true,
337
+ step: "1"
338
+ },
339
+ "x-validator": "integer",
340
+ title: "id"
341
+ }
342
+ },
343
+ {
344
+ name: "title",
345
+ type: "string",
346
+ allowNull: true,
347
+ primaryKey: false,
348
+ unique: false,
349
+ possibleTypes: [
350
+ "string",
351
+ "uuid",
352
+ "nanoid"
353
+ ],
354
+ rawType: "CHARACTER VARYING(255)",
355
+ "interface": "input",
356
+ uiSchema: {
357
+ "x-component": "Input",
358
+ "x-component-props": {
359
+ style: {
360
+ width: "100%"
361
+ }
362
+ },
363
+ title: "title"
364
+ }
365
+ },
366
+ {
367
+ name: "content",
368
+ type: "text",
369
+ allowNull: true,
370
+ primaryKey: false,
371
+ unique: false,
372
+ rawType: "TEXT",
373
+ "interface": "textarea",
374
+ uiSchema: {
375
+ type: "string",
376
+ "x-component": "Input.TextArea",
377
+ title: "content"
378
+ }
379
+ }
380
+ ],
381
+ introspected: true
382
+ }
383
+ ];
384
+ const dataSource2 = {
385
+ key,
386
+ displayName,
387
+ status,
388
+ type,
389
+ isDBInstance,
390
+ collections
391
+ };
392
+ const data = [
393
+ {
394
+ f_o3y6p9gf1gx: null,
395
+ createdAt: "2023-03-30T07:53:10.941Z",
396
+ updatedAt: "2024-04-12T03:27:45.748Z",
397
+ appLang: "zh-CN",
398
+ createdById: null,
399
+ email: "admin@nocobase.com",
400
+ f_2ytvt3phlp2: null,
401
+ f_3jl554hv7lt: null,
402
+ f_51qityssoq1: null,
403
+ f_dybwctlb233: null,
404
+ f_hbegrnglpv2: null,
405
+ f_ndkyrfvh9il: null,
406
+ f_o33xmbd62fj: null,
407
+ f_t52vqdtfv4h: null,
408
+ f_vak0o8efq4v: [],
409
+ id: 1,
410
+ nickname: "Super Admin",
411
+ phone: null,
412
+ systemSettings: {
413
+ theme: "compact",
414
+ themeId: 1
415
+ },
416
+ updatedById: 1,
417
+ username: "nocobase"
418
+ }
419
+ ];
420
+ const usersListData = {
421
+ data
422
+ };
423
+ const mockApi = (axiosInstance, apis = {}) => {
424
+ const mock = new MockAdapter(axiosInstance);
425
+ Object.keys(apis).forEach((key2) => {
426
+ mock.onAny(key2).reply(200, apis[key2]);
427
+ });
428
+ return (apis2 = {}) => {
429
+ Object.keys(apis2).forEach((key2) => {
430
+ mock.onAny(key2).reply(200, apis2[key2]);
431
+ });
432
+ };
433
+ };
434
+ const mockAppApi = (app, apis = {}) => {
435
+ const mock = mockApi(app.apiClient.axios, apis);
436
+ return mock;
437
+ };
438
+ const getApp = (options) => {
439
+ const { appOptions, enableUserListDataBlock, providers, apis, enableMultipleDataSource } = options;
440
+ const app = appOptions instanceof Application ? appOptions : new Application(appOptions);
441
+ if (providers) {
442
+ app.addProviders(providers);
443
+ }
444
+ app.getCollectionManager().addCollections(dataSourceMainCollections);
445
+ if (enableUserListDataBlock && !apis["users:list"]) {
446
+ apis["users:list"] = usersListData;
447
+ }
448
+ if (enableMultipleDataSource) {
449
+ app.dataSourceManager.addDataSource(LocalDataSource, dataSource2);
450
+ }
451
+ mockAppApi(app, apis);
452
+ const App = app.getRootComponent();
453
+ return {
454
+ App,
455
+ app
456
+ };
457
+ };
458
+ const getAppComponent = (options) => {
459
+ const {
460
+ Component,
461
+ enableUserListDataBlock,
462
+ enableMultipleDataSource,
463
+ value,
464
+ props,
465
+ appOptions,
466
+ apis,
467
+ onChange,
468
+ schema: optionsSchema = {}
469
+ } = options;
470
+ const schema = {
471
+ type: "object",
472
+ name: "test",
473
+ default: value,
474
+ "x-component": Component,
475
+ "x-component-props": {
476
+ onChange,
477
+ ...props
478
+ },
479
+ ...optionsSchema
480
+ };
481
+ if (!schema.name) {
482
+ schema.name = "test";
483
+ }
484
+ if (!schema.type) {
485
+ schema.type = "void";
486
+ }
487
+ const TestDemo = () => {
488
+ if (!enableUserListDataBlock) {
489
+ return /* @__PURE__ */ React.createElement(SchemaComponent, { schema });
490
+ }
491
+ return /* @__PURE__ */ React.createElement(DataBlockProvider, { collection: "users", action: "list" }, /* @__PURE__ */ React.createElement(SchemaComponent, { schema }));
492
+ };
493
+ const { App } = getApp({
494
+ appOptions,
495
+ apis,
496
+ providers: [TestDemo],
497
+ enableMultipleDataSource,
498
+ enableUserListDataBlock
499
+ });
500
+ return App;
501
+ };
5
502
  function customRender(ui, options = {}) {
6
503
  return render(ui, {
7
504
  // wrap provider(s) here if needed
@@ -14,9 +511,35 @@ const sleep = async (timeout = 0) => {
14
511
  setTimeout(resolve, timeout);
15
512
  });
16
513
  };
514
+ const WaitApp = async () => {
515
+ await waitFor(() => {
516
+ expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
517
+ });
518
+ };
519
+ const renderHookWithApp = async (options) => {
520
+ const { hook: useHook, props, Wrapper = Fragment, ...otherOptions } = options;
521
+ const { App } = getApp(otherOptions);
522
+ const WrapperValue = ({ children }) => /* @__PURE__ */ React.createElement(App, null, /* @__PURE__ */ React.createElement(Wrapper, null, children));
523
+ const res = renderHook(() => useHook(), { wrapper: WrapperValue, initialProps: props });
524
+ await WaitApp();
525
+ return res;
526
+ };
527
+ const renderApp = async (options) => {
528
+ const App = getAppComponent(options);
529
+ const res = render(/* @__PURE__ */ React.createElement(App, null));
530
+ await WaitApp();
531
+ return res;
532
+ };
533
+ const renderReadPrettyApp = (options) => {
534
+ return renderApp({ ...options, schema: { ...options.schema || {}, "x-read-pretty": true } });
535
+ };
17
536
  export {
537
+ WaitApp,
18
538
  customRender as render,
19
- renderHook,
539
+ renderApp,
540
+ renderHook2 as renderHook,
541
+ renderHookWithApp,
542
+ renderReadPrettyApp,
20
543
  sleep,
21
544
  default2 as userEvent
22
545
  };
@@ -5,6 +5,7 @@ export { defineConfig };
5
5
  export interface CollectionSetting {
6
6
  name: string;
7
7
  title?: string;
8
+ titleField?: string;
8
9
  /**
9
10
  * @default 'general'
10
11
  */
@@ -47,6 +48,7 @@ export interface CollectionSetting {
47
48
  * @default false
48
49
  */
49
50
  inherit?: boolean;
51
+ inherits?: string[];
50
52
  category?: any[];
51
53
  hidden?: boolean;
52
54
  description?: string;
@@ -183,11 +185,21 @@ interface ExtendUtils {
183
185
  mockCollection: (collectionSetting: CollectionSetting) => Promise<any>;
184
186
  /**
185
187
  * 自动生成一条对应 collection 的数据
186
- * @param collectionName 数据表名称
187
- * @param data 自定义的数据,缺失时会生成随机数据
188
188
  * @returns 返回一条生成的数据
189
189
  */
190
- mockRecord: <T = any>(collectionName: string, data?: any) => Promise<T>;
190
+ mockRecord: {
191
+ /**
192
+ * @param collectionName 数据表名称
193
+ * @param data 自定义的数据,缺失时会生成随机数据
194
+ * @param maxDepth - 生成的数据的最大深度,默认为 4,当不想生成太多数据时可以设置一个较低的值
195
+ */
196
+ <T = any>(collectionName: string, data?: any, maxDepth?: number): Promise<T>;
197
+ /**
198
+ * @param collectionName 数据表名称
199
+ * @param maxDepth - 生成的数据的最大深度,默认为 4,当不想生成太多数据时可以设置一个较低的值
200
+ */
201
+ <T = any>(collectionName: string, maxDepth?: number): Promise<T>;
202
+ };
191
203
  /**
192
204
  * 自动生成多条对应 collection 的数据
193
205
  */
@@ -195,13 +207,15 @@ interface ExtendUtils {
195
207
  /**
196
208
  * @param collectionName - 数据表名称
197
209
  * @param count - 生成的数据条数
210
+ * @param maxDepth - 生成的数据的最大深度,默认为 4,当不想生成太多数据时可以设置一个较低的值
198
211
  */
199
- <T = any>(collectionName: string, count?: number): Promise<T[]>;
212
+ <T = any>(collectionName: string, count?: number, maxDepth?: number): Promise<T[]>;
200
213
  /**
201
214
  * @param collectionName - 数据表名称
202
215
  * @param data - 指定生成的数据
216
+ * @param maxDepth - 生成的数据的最大深度,默认为 4,当不想生成太多数据时可以设置一个较低的值
203
217
  */
204
- <T = any>(collectionName: string, data?: any[]): Promise<T[]>;
218
+ <T = any>(collectionName: string, data?: any[], maxDepth?: number): Promise<T[]>;
205
219
  };
206
220
  /**
207
221
  * 该方法已弃用,请使用 mockCollections
@@ -233,6 +247,10 @@ interface ExtendUtils {
233
247
  * @param key 外部数据源key
234
248
  */
235
249
  destoryExternalDataSource: <T = any>(key: string) => Promise<T>;
250
+ /**
251
+ * 清空区块模板
252
+ */
253
+ clearBlockTemplates: () => Promise<void>;
236
254
  }
237
255
  export declare class NocoPage {
238
256
  private options?;
@@ -259,6 +277,12 @@ export declare const test: import("@playwright/test").TestType<import("@playwrig
259
277
  (callback: (args: import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & ExtendUtils & import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions) => boolean, description?: string): void;
260
278
  };
261
279
  };
280
+ /**
281
+ * 将数据表中 mock 出来的 records 删除掉
282
+ * @param collectionName
283
+ * @param records
284
+ */
285
+ export declare const deleteRecords: (collectionName: string, filter: any) => Promise<void>;
262
286
  /**
263
287
  * 删除一些不需要的字段,如 key
264
288
  * @param collectionSettings
@@ -280,10 +304,11 @@ export declare function expectSettingsMenu({ showMenu, supportedOptions, page, u
280
304
  * 辅助断言 Initializer 的菜单项是否存在
281
305
  * @param param0
282
306
  */
283
- export declare function expectInitializerMenu({ showMenu, supportedOptions, page }: {
284
- showMenu: any;
285
- supportedOptions: any;
286
- page: any;
307
+ export declare function expectInitializerMenu({ showMenu, supportedOptions, page, expectValue, }: {
308
+ showMenu: () => Promise<void>;
309
+ supportedOptions: string[];
310
+ page: Page;
311
+ expectValue?: () => Promise<void>;
287
312
  }): Promise<void>;
288
313
  /**
289
314
  * 用于辅助在 page 中创建区块
@@ -291,3 +316,4 @@ export declare function expectInitializerMenu({ showMenu, supportedOptions, page
291
316
  * @param name
292
317
  */
293
318
  export declare const createBlockInPage: (page: Page, name: string) => Promise<void>;
319
+ export declare const mockUserRecordsWithoutDepartments: (mockRecords: ExtendUtils['mockRecords'], count: number) => Promise<any[]>;