@nocobase/client-v2 2.1.26 → 2.1.28
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.
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/index.mjs +69 -69
- package/lib/index.js +76 -76
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +8 -7
- package/src/flow/__tests__/FlowRoute.test.tsx +1 -0
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/components/FlowRoute.tsx +6 -28
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageTabModel.tsx +63 -15
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +489 -6
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/locale/languageCodes.ts +1 -0
|
@@ -59,6 +59,51 @@ vi.mock('ahooks', async (importOriginal) => {
|
|
|
59
59
|
};
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
type RootPageTabModelClass = typeof import('../PageTabModel').RootPageTabModel;
|
|
63
|
+
|
|
64
|
+
type TestRoute = {
|
|
65
|
+
schemaUid?: string;
|
|
66
|
+
options?: Record<string, unknown>;
|
|
67
|
+
} & Record<string, unknown>;
|
|
68
|
+
|
|
69
|
+
type RootPageTabModelTestOptions = {
|
|
70
|
+
props?: {
|
|
71
|
+
route?: TestRoute;
|
|
72
|
+
};
|
|
73
|
+
stepParams?: {
|
|
74
|
+
pageTabSettings?: {
|
|
75
|
+
tab?: {
|
|
76
|
+
title?: string;
|
|
77
|
+
icon?: string;
|
|
78
|
+
documentTitle?: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
context: {
|
|
83
|
+
api: {
|
|
84
|
+
request: ReturnType<typeof vi.fn>;
|
|
85
|
+
};
|
|
86
|
+
routeRepository?: {
|
|
87
|
+
getRouteBySchemaUid?: (schemaUid: string) => TestRoute | undefined;
|
|
88
|
+
refreshAccessible?: () => Promise<unknown>;
|
|
89
|
+
};
|
|
90
|
+
refreshDesktopRoutes?: () => Promise<unknown>;
|
|
91
|
+
logger?: {
|
|
92
|
+
warn?: (context: { err: unknown }, message: string) => void;
|
|
93
|
+
};
|
|
94
|
+
t: (value: string) => string;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type RootPageTabModelTestConstructor = new (
|
|
99
|
+
options: RootPageTabModelTestOptions,
|
|
100
|
+
) => InstanceType<RootPageTabModelClass>;
|
|
101
|
+
|
|
102
|
+
function createRootPageTabModel(Model: RootPageTabModelClass, options: RootPageTabModelTestOptions) {
|
|
103
|
+
const TestModel = Model as unknown as RootPageTabModelTestConstructor;
|
|
104
|
+
return new TestModel(options);
|
|
105
|
+
}
|
|
106
|
+
|
|
62
107
|
describe('PageTabModel', () => {
|
|
63
108
|
beforeEach(() => {
|
|
64
109
|
vi.resetModules();
|
|
@@ -113,7 +158,7 @@ describe('PageTabModel', () => {
|
|
|
113
158
|
it('should persist tab documentTitle to route options on save', async () => {
|
|
114
159
|
const { RootPageTabModel } = await import('../PageTabModel');
|
|
115
160
|
const request = vi.fn().mockResolvedValue({});
|
|
116
|
-
const model =
|
|
161
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
117
162
|
props: {
|
|
118
163
|
route: {
|
|
119
164
|
schemaUid: 'tab-1',
|
|
@@ -132,7 +177,7 @@ describe('PageTabModel', () => {
|
|
|
132
177
|
api: { request },
|
|
133
178
|
t: (value: string) => value,
|
|
134
179
|
},
|
|
135
|
-
}
|
|
180
|
+
});
|
|
136
181
|
|
|
137
182
|
await model.save();
|
|
138
183
|
|
|
@@ -141,6 +186,413 @@ describe('PageTabModel', () => {
|
|
|
141
186
|
expect(call.data.options.documentTitle).toBe('Tab doc title');
|
|
142
187
|
});
|
|
143
188
|
|
|
189
|
+
it('should update a persisted tab route by id with only editable fields and latest options', async () => {
|
|
190
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
191
|
+
const request = vi.fn().mockResolvedValue({});
|
|
192
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
193
|
+
props: {
|
|
194
|
+
route: {
|
|
195
|
+
id: 123,
|
|
196
|
+
parentId: 456,
|
|
197
|
+
schemaUid: 'tab-1',
|
|
198
|
+
type: 'tabs',
|
|
199
|
+
sort: 1,
|
|
200
|
+
hidden: true,
|
|
201
|
+
tabSchemaName: 'tab-name-1',
|
|
202
|
+
hideInMenu: false,
|
|
203
|
+
enableTabs: false,
|
|
204
|
+
roles: [{ name: 'admin', title: 'Admin' }],
|
|
205
|
+
createdAt: '2026-07-16T00:00:00.000Z',
|
|
206
|
+
updatedAt: '2026-07-16T01:00:00.000Z',
|
|
207
|
+
options: {
|
|
208
|
+
badge: {
|
|
209
|
+
showZero: false,
|
|
210
|
+
},
|
|
211
|
+
pluginState: 'stale',
|
|
212
|
+
removedPluginState: 'stale-only',
|
|
213
|
+
flowRegistry: { stale: true },
|
|
214
|
+
documentTitle: 'Stale document title',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
stepParams: {
|
|
219
|
+
pageTabSettings: {
|
|
220
|
+
tab: {
|
|
221
|
+
title: 'Renamed tab',
|
|
222
|
+
icon: 'TableOutlined',
|
|
223
|
+
documentTitle: 'Current document title',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
context: {
|
|
228
|
+
api: { request },
|
|
229
|
+
routeRepository: {
|
|
230
|
+
getRouteBySchemaUid: vi.fn(() => ({
|
|
231
|
+
schemaUid: 'tab-1',
|
|
232
|
+
options: {
|
|
233
|
+
badge: {
|
|
234
|
+
showZero: true,
|
|
235
|
+
},
|
|
236
|
+
pluginState: 'fresh',
|
|
237
|
+
flowRegistry: { fresh: true },
|
|
238
|
+
documentTitle: 'Fresh document title',
|
|
239
|
+
},
|
|
240
|
+
})),
|
|
241
|
+
},
|
|
242
|
+
t: (value: string) => value,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await model.save();
|
|
247
|
+
|
|
248
|
+
const call = request.mock.calls[0][0];
|
|
249
|
+
expect(call).toEqual({
|
|
250
|
+
method: 'post',
|
|
251
|
+
url: 'desktopRoutes:update?filter[id]=123',
|
|
252
|
+
data: {
|
|
253
|
+
schemaUid: 'tab-1',
|
|
254
|
+
title: 'Renamed tab',
|
|
255
|
+
icon: 'TableOutlined',
|
|
256
|
+
options: {
|
|
257
|
+
badge: {
|
|
258
|
+
showZero: true,
|
|
259
|
+
},
|
|
260
|
+
pluginState: 'fresh',
|
|
261
|
+
flowRegistry: {},
|
|
262
|
+
documentTitle: 'Current document title',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
expect(Object.keys(call.data).sort()).toEqual(['icon', 'options', 'schemaUid', 'title']);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should fall back to model route options when the current route is unavailable', async () => {
|
|
270
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
271
|
+
const request = vi.fn().mockResolvedValue({});
|
|
272
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
273
|
+
props: {
|
|
274
|
+
route: {
|
|
275
|
+
id: 123,
|
|
276
|
+
parentId: 456,
|
|
277
|
+
schemaUid: 'tab-1',
|
|
278
|
+
type: 'tabs',
|
|
279
|
+
sort: 1,
|
|
280
|
+
hidden: true,
|
|
281
|
+
options: {
|
|
282
|
+
badge: {
|
|
283
|
+
showZero: true,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
stepParams: {
|
|
289
|
+
pageTabSettings: {
|
|
290
|
+
tab: {
|
|
291
|
+
title: 'Renamed tab',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
context: {
|
|
296
|
+
api: { request },
|
|
297
|
+
routeRepository: {
|
|
298
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
299
|
+
},
|
|
300
|
+
t: (value: string) => value,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
await model.save();
|
|
305
|
+
|
|
306
|
+
const call = request.mock.calls[0][0];
|
|
307
|
+
expect(call).toEqual({
|
|
308
|
+
method: 'post',
|
|
309
|
+
url: 'desktopRoutes:update?filter[id]=123',
|
|
310
|
+
data: {
|
|
311
|
+
schemaUid: 'tab-1',
|
|
312
|
+
title: 'Renamed tab',
|
|
313
|
+
icon: undefined,
|
|
314
|
+
options: {
|
|
315
|
+
badge: {
|
|
316
|
+
showZero: true,
|
|
317
|
+
},
|
|
318
|
+
flowRegistry: {},
|
|
319
|
+
documentTitle: undefined,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should await a route refresh after the request before a subsequent persisted tab save', async () => {
|
|
326
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
327
|
+
const events: string[] = [];
|
|
328
|
+
let resolveFirstRefresh: () => void = () => {};
|
|
329
|
+
const firstRefreshDone = new Promise<void>((resolve) => {
|
|
330
|
+
resolveFirstRefresh = resolve;
|
|
331
|
+
});
|
|
332
|
+
let currentRoute = {
|
|
333
|
+
schemaUid: 'tab-1',
|
|
334
|
+
options: {
|
|
335
|
+
pluginState: 'before-save',
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
let requestCall = 0;
|
|
339
|
+
const request = vi.fn(async () => {
|
|
340
|
+
requestCall += 1;
|
|
341
|
+
events.push(`request:${requestCall}`);
|
|
342
|
+
if (requestCall === 1) {
|
|
343
|
+
return {
|
|
344
|
+
data: {
|
|
345
|
+
data: [
|
|
346
|
+
{
|
|
347
|
+
id: 123,
|
|
348
|
+
schemaUid: 'tab-1',
|
|
349
|
+
options: {
|
|
350
|
+
pluginState: 'after-save',
|
|
351
|
+
serverHookState: true,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return {};
|
|
359
|
+
});
|
|
360
|
+
let refreshCall = 0;
|
|
361
|
+
const refreshAccessible = vi.fn(async () => {
|
|
362
|
+
refreshCall += 1;
|
|
363
|
+
events.push(`refresh:${refreshCall}:start`);
|
|
364
|
+
if (refreshCall === 1) {
|
|
365
|
+
await firstRefreshDone;
|
|
366
|
+
currentRoute = {
|
|
367
|
+
schemaUid: 'tab-1',
|
|
368
|
+
options: {
|
|
369
|
+
pluginState: 'after-save',
|
|
370
|
+
serverHookState: true,
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
events.push(`refresh:${refreshCall}:end`);
|
|
375
|
+
});
|
|
376
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
377
|
+
props: {
|
|
378
|
+
route: {
|
|
379
|
+
id: 123,
|
|
380
|
+
schemaUid: 'tab-1',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
stepParams: {
|
|
384
|
+
pageTabSettings: {
|
|
385
|
+
tab: {
|
|
386
|
+
title: 'Renamed tab',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
context: {
|
|
391
|
+
api: { request },
|
|
392
|
+
routeRepository: {
|
|
393
|
+
getRouteBySchemaUid: vi.fn(() => currentRoute),
|
|
394
|
+
refreshAccessible,
|
|
395
|
+
},
|
|
396
|
+
t: (value: string) => value,
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
let firstSaveResolved = false;
|
|
401
|
+
const firstSave = model.save().then(() => {
|
|
402
|
+
firstSaveResolved = true;
|
|
403
|
+
events.push('save:1:end');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await vi.waitFor(() => expect(refreshAccessible).toHaveBeenCalledTimes(1));
|
|
407
|
+
expect(events.slice(0, 2)).toEqual(['request:1', 'refresh:1:start']);
|
|
408
|
+
await Promise.resolve();
|
|
409
|
+
expect(firstSaveResolved).toBe(false);
|
|
410
|
+
|
|
411
|
+
resolveFirstRefresh();
|
|
412
|
+
await firstSave;
|
|
413
|
+
expect(events.indexOf('request:1')).toBeLessThan(events.indexOf('refresh:1:start'));
|
|
414
|
+
expect(events.indexOf('refresh:1:end')).toBeLessThan(events.indexOf('save:1:end'));
|
|
415
|
+
|
|
416
|
+
await model.save();
|
|
417
|
+
|
|
418
|
+
expect(refreshAccessible).toHaveBeenCalledTimes(2);
|
|
419
|
+
expect(request.mock.calls[1][0].data.options).toMatchObject({
|
|
420
|
+
pluginState: 'after-save',
|
|
421
|
+
serverHookState: true,
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it('should serialize route refreshes when new tabs are saved concurrently', async () => {
|
|
426
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
427
|
+
let releaseFirstRefresh: () => void = () => {};
|
|
428
|
+
const firstRefreshDone = new Promise<void>((resolve) => {
|
|
429
|
+
releaseFirstRefresh = resolve;
|
|
430
|
+
});
|
|
431
|
+
let refreshCall = 0;
|
|
432
|
+
let activeRefreshes = 0;
|
|
433
|
+
let maxActiveRefreshes = 0;
|
|
434
|
+
const refreshAccessible = vi.fn(async () => {
|
|
435
|
+
refreshCall += 1;
|
|
436
|
+
activeRefreshes += 1;
|
|
437
|
+
maxActiveRefreshes = Math.max(maxActiveRefreshes, activeRefreshes);
|
|
438
|
+
if (refreshCall === 1) {
|
|
439
|
+
await firstRefreshDone;
|
|
440
|
+
}
|
|
441
|
+
activeRefreshes -= 1;
|
|
442
|
+
});
|
|
443
|
+
const request = vi
|
|
444
|
+
.fn()
|
|
445
|
+
.mockResolvedValueOnce({
|
|
446
|
+
data: {
|
|
447
|
+
data: {
|
|
448
|
+
id: 101,
|
|
449
|
+
schemaUid: 'tab-1',
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
})
|
|
453
|
+
.mockResolvedValueOnce({
|
|
454
|
+
data: {
|
|
455
|
+
data: {
|
|
456
|
+
id: 102,
|
|
457
|
+
schemaUid: 'tab-2',
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
const routeRepository = {
|
|
462
|
+
refreshAccessible,
|
|
463
|
+
};
|
|
464
|
+
const createModel = (schemaUid: string, title: string) =>
|
|
465
|
+
createRootPageTabModel(RootPageTabModel, {
|
|
466
|
+
props: {
|
|
467
|
+
route: {
|
|
468
|
+
parentId: 999,
|
|
469
|
+
schemaUid,
|
|
470
|
+
tabSchemaName: `${schemaUid}-name`,
|
|
471
|
+
type: 'tabs',
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
stepParams: {
|
|
475
|
+
pageTabSettings: {
|
|
476
|
+
tab: {
|
|
477
|
+
title,
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
context: {
|
|
482
|
+
api: { request },
|
|
483
|
+
routeRepository,
|
|
484
|
+
t: (value: string) => value,
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const saves = Promise.all([createModel('tab-1', 'Tab 1').save(), createModel('tab-2', 'Tab 2').save()]);
|
|
489
|
+
|
|
490
|
+
await vi.waitFor(() => expect(request).toHaveBeenCalledTimes(2));
|
|
491
|
+
await vi.waitFor(() => expect(refreshAccessible).toHaveBeenCalled());
|
|
492
|
+
await Promise.resolve();
|
|
493
|
+
await Promise.resolve();
|
|
494
|
+
const refreshCallsBeforeRelease = refreshAccessible.mock.calls.length;
|
|
495
|
+
|
|
496
|
+
releaseFirstRefresh();
|
|
497
|
+
await saves;
|
|
498
|
+
|
|
499
|
+
expect(refreshCallsBeforeRelease).toBe(1);
|
|
500
|
+
expect(refreshAccessible).toHaveBeenCalledTimes(2);
|
|
501
|
+
expect(maxActiveRefreshes).toBe(1);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should keep a successful route save when refreshing the route cache fails', async () => {
|
|
505
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
506
|
+
const refreshError = new Error('refresh failed');
|
|
507
|
+
const request = vi
|
|
508
|
+
.fn()
|
|
509
|
+
.mockResolvedValueOnce({
|
|
510
|
+
data: {
|
|
511
|
+
data: {
|
|
512
|
+
id: 123,
|
|
513
|
+
schemaUid: 'tab-1',
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
})
|
|
517
|
+
.mockResolvedValueOnce({});
|
|
518
|
+
const refreshAccessible = vi.fn().mockRejectedValueOnce(refreshError).mockResolvedValueOnce([]);
|
|
519
|
+
const warn = vi.fn();
|
|
520
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
521
|
+
props: {
|
|
522
|
+
route: {
|
|
523
|
+
parentId: 999,
|
|
524
|
+
schemaUid: 'tab-1',
|
|
525
|
+
tabSchemaName: 'tab-name-1',
|
|
526
|
+
type: 'tabs',
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
stepParams: {
|
|
530
|
+
pageTabSettings: {
|
|
531
|
+
tab: {
|
|
532
|
+
title: 'Tab title',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
context: {
|
|
537
|
+
api: { request },
|
|
538
|
+
routeRepository: {
|
|
539
|
+
refreshAccessible,
|
|
540
|
+
},
|
|
541
|
+
logger: { warn },
|
|
542
|
+
t: (value: string) => value,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
await expect(model.save()).resolves.toBeUndefined();
|
|
547
|
+
|
|
548
|
+
expect(model.props.route.id).toBe(123);
|
|
549
|
+
expect(warn).toHaveBeenCalledWith(
|
|
550
|
+
{ err: refreshError },
|
|
551
|
+
'[client-v2] Failed to refresh desktop routes after saving a page tab',
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
await expect(model.save()).resolves.toBeUndefined();
|
|
555
|
+
expect(refreshAccessible).toHaveBeenCalledTimes(2);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('should still reject when writing the tab route fails', async () => {
|
|
559
|
+
const { RootPageTabModel } = await import('../PageTabModel');
|
|
560
|
+
const writeError = new Error('write failed');
|
|
561
|
+
const request = vi.fn().mockRejectedValue(writeError);
|
|
562
|
+
const refreshAccessible = vi.fn().mockResolvedValue([]);
|
|
563
|
+
const warn = vi.fn();
|
|
564
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
565
|
+
props: {
|
|
566
|
+
route: {
|
|
567
|
+
parentId: 999,
|
|
568
|
+
schemaUid: 'tab-1',
|
|
569
|
+
tabSchemaName: 'tab-name-1',
|
|
570
|
+
type: 'tabs',
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
stepParams: {
|
|
574
|
+
pageTabSettings: {
|
|
575
|
+
tab: {
|
|
576
|
+
title: 'Tab title',
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
context: {
|
|
581
|
+
api: { request },
|
|
582
|
+
routeRepository: {
|
|
583
|
+
refreshAccessible,
|
|
584
|
+
},
|
|
585
|
+
logger: { warn },
|
|
586
|
+
t: (value: string) => value,
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
await expect(model.save()).rejects.toBe(writeError);
|
|
591
|
+
expect(refreshAccessible).not.toHaveBeenCalled();
|
|
592
|
+
expect(warn).not.toHaveBeenCalled();
|
|
593
|
+
expect(model.props.route.id).toBeUndefined();
|
|
594
|
+
});
|
|
595
|
+
|
|
144
596
|
it('should sync persisted route id after save', async () => {
|
|
145
597
|
const { RootPageTabModel } = await import('../PageTabModel');
|
|
146
598
|
const request = vi.fn().mockResolvedValue({
|
|
@@ -152,16 +604,24 @@ describe('PageTabModel', () => {
|
|
|
152
604
|
},
|
|
153
605
|
},
|
|
154
606
|
});
|
|
155
|
-
const model =
|
|
607
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
156
608
|
props: {
|
|
157
609
|
route: {
|
|
610
|
+
parentId: 999,
|
|
158
611
|
schemaUid: 'tab-1',
|
|
612
|
+
tabSchemaName: 'tab-name-1',
|
|
613
|
+
type: 'tabs',
|
|
614
|
+
params: [],
|
|
615
|
+
hideInMenu: false,
|
|
616
|
+
enableTabs: false,
|
|
159
617
|
},
|
|
160
618
|
},
|
|
161
619
|
stepParams: {
|
|
162
620
|
pageTabSettings: {
|
|
163
621
|
tab: {
|
|
164
622
|
title: 'Tab title',
|
|
623
|
+
icon: 'TableOutlined',
|
|
624
|
+
documentTitle: 'Tab document title',
|
|
165
625
|
},
|
|
166
626
|
},
|
|
167
627
|
},
|
|
@@ -169,10 +629,33 @@ describe('PageTabModel', () => {
|
|
|
169
629
|
api: { request },
|
|
170
630
|
t: (value: string) => value,
|
|
171
631
|
},
|
|
172
|
-
}
|
|
632
|
+
});
|
|
173
633
|
|
|
174
634
|
await model.save();
|
|
175
635
|
|
|
636
|
+
const call = request.mock.calls[0][0];
|
|
637
|
+
expect(call).toEqual({
|
|
638
|
+
method: 'post',
|
|
639
|
+
url: 'desktopRoutes:updateOrCreate',
|
|
640
|
+
params: {
|
|
641
|
+
filterKeys: ['schemaUid'],
|
|
642
|
+
},
|
|
643
|
+
data: {
|
|
644
|
+
parentId: 999,
|
|
645
|
+
schemaUid: 'tab-1',
|
|
646
|
+
tabSchemaName: 'tab-name-1',
|
|
647
|
+
type: 'tabs',
|
|
648
|
+
params: [],
|
|
649
|
+
hideInMenu: false,
|
|
650
|
+
enableTabs: false,
|
|
651
|
+
title: 'Tab title',
|
|
652
|
+
icon: 'TableOutlined',
|
|
653
|
+
options: {
|
|
654
|
+
flowRegistry: {},
|
|
655
|
+
documentTitle: 'Tab document title',
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
});
|
|
176
659
|
expect(model.props.route.id).toBe(123);
|
|
177
660
|
expect(model.props.route.sort).toBe(9);
|
|
178
661
|
});
|
|
@@ -192,7 +675,7 @@ describe('PageTabModel', () => {
|
|
|
192
675
|
],
|
|
193
676
|
},
|
|
194
677
|
});
|
|
195
|
-
const model =
|
|
678
|
+
const model = createRootPageTabModel(RootPageTabModel, {
|
|
196
679
|
props: {
|
|
197
680
|
route: {
|
|
198
681
|
schemaUid: 'tab-1',
|
|
@@ -212,7 +695,7 @@ describe('PageTabModel', () => {
|
|
|
212
695
|
api: { request },
|
|
213
696
|
t: (value: string) => value,
|
|
214
697
|
},
|
|
215
|
-
}
|
|
698
|
+
});
|
|
216
699
|
|
|
217
700
|
await model.save();
|
|
218
701
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, FlowModelContext, MultiRecordResource } from '@nocobase/flow-engine';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { CollectionBlockModel } from '../CollectionBlockModel';
|
|
13
|
+
|
|
14
|
+
class AddAppendsCollectionBlockModel extends CollectionBlockModel {
|
|
15
|
+
createResource(ctx: FlowModelContext) {
|
|
16
|
+
return ctx.createResource(MultiRecordResource);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('CollectionBlockModel addAppends', () => {
|
|
21
|
+
it('does not throw for a nested field path when the collection is unavailable', () => {
|
|
22
|
+
const engine = new FlowEngine();
|
|
23
|
+
engine.registerModels({ AddAppendsCollectionBlockModel });
|
|
24
|
+
|
|
25
|
+
const model = engine.createModel<AddAppendsCollectionBlockModel>({
|
|
26
|
+
uid: 'missing-collection-add-appends-block',
|
|
27
|
+
use: 'AddAppendsCollectionBlockModel',
|
|
28
|
+
stepParams: {
|
|
29
|
+
resourceSettings: {
|
|
30
|
+
init: {
|
|
31
|
+
dataSourceKey: 'main',
|
|
32
|
+
collectionName: 'missing_collection',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(model.collection).toBeUndefined();
|
|
39
|
+
expect(() => model.addAppends('createdBy.departments')).not.toThrow();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -54,6 +54,7 @@ export const languageCodes: Record<string, LocaleOptions> = {
|
|
|
54
54
|
'ml-IN': { label: 'മലയാളം' },
|
|
55
55
|
'mn-MN': { label: 'Монгол хэл' },
|
|
56
56
|
'ms-MY': { label: 'بهاس ملايو' },
|
|
57
|
+
'my-MM': { label: 'မြန်မာဘာသာ' },
|
|
57
58
|
'nb-NO': { label: 'Norsk bokmål' },
|
|
58
59
|
'ne-NP': { label: 'नेपाली' },
|
|
59
60
|
'nl-BE': { label: 'Vlaams' },
|