@nocobase/client-v2 2.1.8 → 2.1.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.
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.mjs +75 -49
- package/lib/index.js +81 -55
- package/package.json +7 -7
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/linkageRules.tsx +75 -16
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/components/FieldAssignRulesEditor.tsx +62 -12
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +81 -4
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +30 -1
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
- package/src/flow/models/blocks/form/value-runtime/rules.ts +443 -11
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/fields/InputFieldModel.tsx +14 -22
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +17 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
|
@@ -795,15 +795,23 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
795
795
|
);
|
|
796
796
|
|
|
797
797
|
expect(screen.getByLabelText('Default value')).toBeInTheDocument();
|
|
798
|
+
expect(screen.getByLabelText('Override value')).toBeInTheDocument();
|
|
798
799
|
expect(screen.getByLabelText('Fixed value')).toBeInTheDocument();
|
|
799
800
|
expect(screen.getByLabelText('Fixed value')).toBeChecked();
|
|
800
801
|
await userEvent.click(screen.getByLabelText('Default value'));
|
|
801
802
|
await waitFor(() => {
|
|
802
803
|
expect(onChange).toHaveBeenCalled();
|
|
803
804
|
});
|
|
804
|
-
|
|
805
|
-
const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
805
|
+
let lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
806
806
|
expect(lastCall?.[0]?.mode).toBe('default');
|
|
807
|
+
|
|
808
|
+
onChange.mockClear();
|
|
809
|
+
await userEvent.click(screen.getByLabelText('Override value'));
|
|
810
|
+
await waitFor(() => {
|
|
811
|
+
expect(onChange).toHaveBeenCalled();
|
|
812
|
+
});
|
|
813
|
+
lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
814
|
+
expect(lastCall?.[0]?.mode).toBe('override');
|
|
807
815
|
});
|
|
808
816
|
|
|
809
817
|
it('shows assignment mode tooltips for each option', async () => {
|
|
@@ -821,7 +829,7 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
821
829
|
);
|
|
822
830
|
|
|
823
831
|
const questionIcons = Array.from(container.querySelectorAll('.anticon-question-circle'));
|
|
824
|
-
expect(questionIcons).toHaveLength(
|
|
832
|
+
expect(questionIcons).toHaveLength(3);
|
|
825
833
|
|
|
826
834
|
await userEvent.hover(questionIcons[0] as Element);
|
|
827
835
|
await waitFor(() => {
|
|
@@ -835,11 +843,80 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
835
843
|
await userEvent.unhover(questionIcons[0] as Element);
|
|
836
844
|
|
|
837
845
|
await userEvent.hover(questionIcons[1] as Element);
|
|
846
|
+
await waitFor(() => {
|
|
847
|
+
expect(screen.getByText('Overrides existing values until the user edits it.')).toBeInTheDocument();
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
await userEvent.unhover(questionIcons[1] as Element);
|
|
851
|
+
|
|
852
|
+
await userEvent.hover(questionIcons[2] as Element);
|
|
838
853
|
await waitFor(() => {
|
|
839
854
|
expect(screen.getByText('A system-set value. Read-only.')).toBeInTheDocument();
|
|
840
855
|
});
|
|
841
856
|
});
|
|
842
857
|
|
|
858
|
+
it('honors allowed assignment modes', () => {
|
|
859
|
+
const value: FieldAssignRuleItem[] = [
|
|
860
|
+
{
|
|
861
|
+
key: '1',
|
|
862
|
+
enable: true,
|
|
863
|
+
targetPath: 'title',
|
|
864
|
+
mode: 'override',
|
|
865
|
+
},
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
render(
|
|
869
|
+
wrap(
|
|
870
|
+
<FieldAssignRulesEditor
|
|
871
|
+
t={t}
|
|
872
|
+
fieldOptions={[]}
|
|
873
|
+
value={value}
|
|
874
|
+
allowedModes={['default', 'assign']}
|
|
875
|
+
showCondition={false}
|
|
876
|
+
showEnable={false}
|
|
877
|
+
/>,
|
|
878
|
+
),
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
expect(screen.getByLabelText('Default value')).toBeInTheDocument();
|
|
882
|
+
expect(screen.getByLabelText('Fixed value')).toBeInTheDocument();
|
|
883
|
+
expect(screen.queryByLabelText('Override value')).toBeNull();
|
|
884
|
+
expect(screen.getByLabelText('Fixed value')).toBeChecked();
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('normalizes disallowed assignment mode before emitting changes', async () => {
|
|
888
|
+
const onChange = vi.fn();
|
|
889
|
+
const value: FieldAssignRuleItem[] = [
|
|
890
|
+
{
|
|
891
|
+
key: '1',
|
|
892
|
+
enable: true,
|
|
893
|
+
targetPath: 'title',
|
|
894
|
+
mode: 'override',
|
|
895
|
+
},
|
|
896
|
+
];
|
|
897
|
+
|
|
898
|
+
render(
|
|
899
|
+
wrap(
|
|
900
|
+
<FieldAssignRulesEditor
|
|
901
|
+
t={t}
|
|
902
|
+
fieldOptions={[]}
|
|
903
|
+
value={value}
|
|
904
|
+
onChange={onChange}
|
|
905
|
+
allowedModes={['default', 'assign']}
|
|
906
|
+
showCondition={false}
|
|
907
|
+
/>,
|
|
908
|
+
),
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
await userEvent.click(screen.getByRole('switch'));
|
|
912
|
+
|
|
913
|
+
await waitFor(() => {
|
|
914
|
+
expect(onChange).toHaveBeenCalled();
|
|
915
|
+
});
|
|
916
|
+
const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
917
|
+
expect(lastCall?.[0]?.mode).toBe('assign');
|
|
918
|
+
});
|
|
919
|
+
|
|
843
920
|
it('merges extra item tree into base item while keeping order', () => {
|
|
844
921
|
const base: MetaTreeNode[] = [
|
|
845
922
|
{ name: 'formValues', title: 'Current form', type: 'object', paths: ['formValues'] },
|
|
@@ -897,7 +974,7 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
897
974
|
|
|
898
975
|
const itemNode = merged.find((node) => node.name === 'item');
|
|
899
976
|
expect(itemNode).toBeTruthy();
|
|
900
|
-
const parent = (itemNode
|
|
977
|
+
const parent = ((itemNode?.children as MetaTreeNode[]) || []).find((node) => node.name === 'parentItem');
|
|
901
978
|
expect(parent).toBeTruthy();
|
|
902
979
|
|
|
903
980
|
const nestedParent = ((parent?.children as MetaTreeNode[]) || []).find((node) => node.name === 'parentItem');
|
|
@@ -375,12 +375,15 @@ export class PageModel extends FlowModel<PageModelStructure> {
|
|
|
375
375
|
tabBarStyle={this.props.tabBarStyle}
|
|
376
376
|
items={this.mapTabs()}
|
|
377
377
|
onChange={(activeKey) => {
|
|
378
|
+
const previousActiveKey = this.props.tabActiveKey || this.getActiveTabKey();
|
|
378
379
|
this.context.view.navigation?.changeTo?.({
|
|
379
380
|
tabUid: activeKey,
|
|
380
381
|
});
|
|
381
382
|
|
|
382
383
|
this.invokeTabModelLifecycleMethod(activeKey, 'onActive');
|
|
383
|
-
|
|
384
|
+
if (previousActiveKey && previousActiveKey !== activeKey) {
|
|
385
|
+
this.invokeTabModelLifecycleMethod(previousActiveKey, 'onInactive');
|
|
386
|
+
}
|
|
384
387
|
this.setProps('tabActiveKey', activeKey);
|
|
385
388
|
}}
|
|
386
389
|
// destroyInactiveTabPane
|
|
@@ -452,6 +452,34 @@ describe('PageModel', () => {
|
|
|
452
452
|
marginInlineEnd: 24,
|
|
453
453
|
});
|
|
454
454
|
});
|
|
455
|
+
|
|
456
|
+
it('should deactivate the previous tab when navigation updates active tab synchronously', () => {
|
|
457
|
+
pageModel.props = { tabActiveKey: 'tab-old' } as any;
|
|
458
|
+
const invokeSpy = vi.spyOn(pageModel as any, 'invokeTabModelLifecycleMethod').mockImplementation(() => undefined);
|
|
459
|
+
const changeTo = vi.fn((params: { tabUid: string }) => {
|
|
460
|
+
pageModel.props.tabActiveKey = params.tabUid;
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
pageModel.context.view = {
|
|
464
|
+
navigation: {
|
|
465
|
+
viewParams: {
|
|
466
|
+
tabUid: 'tab-old',
|
|
467
|
+
},
|
|
468
|
+
changeTo,
|
|
469
|
+
},
|
|
470
|
+
} as any;
|
|
471
|
+
|
|
472
|
+
const result = pageModel.renderTabs() as any;
|
|
473
|
+
const tabsElement = result.props.children;
|
|
474
|
+
|
|
475
|
+
tabsElement.props.onChange('tab-new');
|
|
476
|
+
|
|
477
|
+
expect(changeTo).toHaveBeenCalledWith({ tabUid: 'tab-new' });
|
|
478
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(1, 'tab-new', 'onActive');
|
|
479
|
+
expect(invokeSpy).toHaveBeenNthCalledWith(2, 'tab-old', 'onInactive');
|
|
480
|
+
expect(invokeSpy).not.toHaveBeenCalledWith('tab-new', 'onInactive');
|
|
481
|
+
expect(pageModel.props.tabActiveKey).toBe('tab-new');
|
|
482
|
+
});
|
|
455
483
|
});
|
|
456
484
|
|
|
457
485
|
describe('render header spacing with tabs', () => {
|
|
@@ -231,6 +231,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
231
231
|
private initialDefaultsPromise?: Promise<void>;
|
|
232
232
|
private initialRefreshHandledTargetIds = new Set<string>();
|
|
233
233
|
private lastDefaultValueByFieldName = new Map<string, any>();
|
|
234
|
+
private userEditedFieldNames = new Set<string>();
|
|
234
235
|
private defaultValuesRefreshSeq = 0;
|
|
235
236
|
|
|
236
237
|
get form() {
|
|
@@ -436,6 +437,28 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
436
437
|
return isEqual(current, this.lastDefaultValueByFieldName.get(name));
|
|
437
438
|
}
|
|
438
439
|
|
|
440
|
+
private canApplyFormOverrideValue(name: string, force?: boolean) {
|
|
441
|
+
if (force) return true;
|
|
442
|
+
return !this.userEditedFieldNames.has(name);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private normalizeFieldValueMode(mode: unknown): 'default' | 'assign' | 'override' {
|
|
446
|
+
if (mode === 'assign') return 'assign';
|
|
447
|
+
if (mode === 'override') return 'override';
|
|
448
|
+
return 'default';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private markFilterFormUserEditedFields(changedValues: any) {
|
|
452
|
+
if (!changedValues || typeof changedValues !== 'object' || Array.isArray(changedValues)) return;
|
|
453
|
+
for (const name of Object.keys(changedValues)) {
|
|
454
|
+
this.userEditedFieldNames.add(String(name));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private resetFilterFormUserEditedFields() {
|
|
459
|
+
this.userEditedFieldNames.clear();
|
|
460
|
+
}
|
|
461
|
+
|
|
439
462
|
private async matchDefaultValueCondition(condition: any) {
|
|
440
463
|
if (!condition) return true;
|
|
441
464
|
|
|
@@ -458,6 +481,10 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
458
481
|
if (!form) return appliedValues;
|
|
459
482
|
|
|
460
483
|
const force = options?.force === true;
|
|
484
|
+
if (force) {
|
|
485
|
+
this.resetFilterFormUserEditedFields();
|
|
486
|
+
}
|
|
487
|
+
|
|
461
488
|
const params = this.getStepParams?.('formFilterBlockModelSettings', 'defaultValues');
|
|
462
489
|
const rules = (params?.value || []) as any[];
|
|
463
490
|
if (!Array.isArray(rules) || rules.length === 0) return appliedValues;
|
|
@@ -499,8 +526,9 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
499
526
|
|
|
500
527
|
const operator = getDefaultOperator(itemModel as any);
|
|
501
528
|
const normalized = normalizeFilterValueByOperator(operator, resolved);
|
|
502
|
-
const mode =
|
|
529
|
+
const mode = this.normalizeFieldValueMode(rule.mode);
|
|
503
530
|
if (mode === 'default' && !this.canApplyFormDefaultValue(String(name), current, force)) continue;
|
|
531
|
+
if (mode === 'override' && !this.canApplyFormOverrideValue(String(name), force)) continue;
|
|
504
532
|
if (isEqual(current, normalized)) {
|
|
505
533
|
if (mode === 'default') {
|
|
506
534
|
this.lastDefaultValueByFieldName.set(String(name), normalized);
|
|
@@ -527,6 +555,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
527
555
|
}
|
|
528
556
|
|
|
529
557
|
private handleFilterFormValuesChange(changedValues: any, allValues: any) {
|
|
558
|
+
this.markFilterFormUserEditedFields(changedValues);
|
|
530
559
|
const refreshSeq = ++this.defaultValuesRefreshSeq;
|
|
531
560
|
void (async () => {
|
|
532
561
|
const appliedValues = await this.applyFormDefaultValues({ refreshSeq });
|
|
@@ -50,6 +50,7 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
|
|
|
50
50
|
const model = {
|
|
51
51
|
defaultValuesRefreshSeq: 0,
|
|
52
52
|
lastDefaultValueByFieldName: new Map<string, any>(),
|
|
53
|
+
userEditedFieldNames: new Set<string>(),
|
|
53
54
|
form: {
|
|
54
55
|
getFieldsValue: () => ({ ...values }),
|
|
55
56
|
getFieldValue: (name: string) => values[name],
|
|
@@ -86,6 +87,10 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
|
|
|
86
87
|
return undefined;
|
|
87
88
|
}),
|
|
88
89
|
canApplyFormDefaultValue: (FilterFormBlockModel.prototype as any).canApplyFormDefaultValue,
|
|
90
|
+
canApplyFormOverrideValue: (FilterFormBlockModel.prototype as any).canApplyFormOverrideValue,
|
|
91
|
+
normalizeFieldValueMode: (FilterFormBlockModel.prototype as any).normalizeFieldValueMode,
|
|
92
|
+
markFilterFormUserEditedFields: (FilterFormBlockModel.prototype as any).markFilterFormUserEditedFields,
|
|
93
|
+
resetFilterFormUserEditedFields: (FilterFormBlockModel.prototype as any).resetFilterFormUserEditedFields,
|
|
89
94
|
matchDefaultValueCondition: (FilterFormBlockModel.prototype as any).matchDefaultValueCondition,
|
|
90
95
|
applyFormDefaultValues: FilterFormBlockModel.prototype.applyFormDefaultValues,
|
|
91
96
|
handleFilterFormValuesChange: (FilterFormBlockModel.prototype as any).handleFilterFormValuesChange,
|
|
@@ -315,6 +320,78 @@ describe('filter-form defaultValues wiring', () => {
|
|
|
315
320
|
expect(values.username_user).toBe('Bob');
|
|
316
321
|
});
|
|
317
322
|
|
|
323
|
+
it('applies override values until the target filter field is changed by user', async () => {
|
|
324
|
+
const { model, values } = createFilterFormDefaultValuesModel(
|
|
325
|
+
[
|
|
326
|
+
{
|
|
327
|
+
key: 'username-override',
|
|
328
|
+
enable: true,
|
|
329
|
+
targetPath: 'username',
|
|
330
|
+
mode: 'override',
|
|
331
|
+
value: '{{ ctx.formValues.nickname_nick }}',
|
|
332
|
+
},
|
|
333
|
+
],
|
|
334
|
+
{ nickname_nick: 'Bob', username_user: 'Manual' },
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
338
|
+
expect(values.username_user).toBe('Bob');
|
|
339
|
+
|
|
340
|
+
values.nickname_nick = 'Carol';
|
|
341
|
+
model.defaultValuesRefreshSeq += 1;
|
|
342
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, {
|
|
343
|
+
refreshSeq: model.defaultValuesRefreshSeq,
|
|
344
|
+
});
|
|
345
|
+
expect(values.username_user).toBe('Carol');
|
|
346
|
+
|
|
347
|
+
values.username_user = 'User value';
|
|
348
|
+
(model as any).handleFilterFormValuesChange(
|
|
349
|
+
{ username_user: 'User value' },
|
|
350
|
+
{ nickname_nick: 'Carol', username_user: 'User value' },
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
await waitFor(() => {
|
|
354
|
+
expect(values.username_user).toBe('User value');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
values.nickname_nick = 'Dora';
|
|
358
|
+
model.defaultValuesRefreshSeq += 1;
|
|
359
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, {
|
|
360
|
+
refreshSeq: model.defaultValuesRefreshSeq,
|
|
361
|
+
});
|
|
362
|
+
expect(values.username_user).toBe('User value');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('clears filter form user-edited override state on forced apply', async () => {
|
|
366
|
+
const { model, values } = createFilterFormDefaultValuesModel(
|
|
367
|
+
[
|
|
368
|
+
{
|
|
369
|
+
key: 'username-override',
|
|
370
|
+
enable: true,
|
|
371
|
+
targetPath: 'username',
|
|
372
|
+
mode: 'override',
|
|
373
|
+
condition: {
|
|
374
|
+
logic: '$and',
|
|
375
|
+
items: [{ path: '{{ ctx.formValues.nickname_nick }}', operator: '$eq', value: 'allow' }],
|
|
376
|
+
},
|
|
377
|
+
value: '{{ ctx.formValues.nickname_nick }}',
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
{ nickname_nick: 'deny', username_user: 'User value' },
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
model.userEditedFieldNames.add('username_user');
|
|
384
|
+
|
|
385
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any, { force: true });
|
|
386
|
+
expect(values.username_user).toBe('User value');
|
|
387
|
+
expect(model.userEditedFieldNames.has('username_user')).toBe(false);
|
|
388
|
+
|
|
389
|
+
values.nickname_nick = 'allow';
|
|
390
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
391
|
+
|
|
392
|
+
expect(values.username_user).toBe('allow');
|
|
393
|
+
});
|
|
394
|
+
|
|
318
395
|
it('skips filter form field values when the rule condition does not match', async () => {
|
|
319
396
|
const { model, values } = createFilterFormDefaultValuesModel(
|
|
320
397
|
[
|
|
@@ -166,6 +166,13 @@ export class FormBlockModel<
|
|
|
166
166
|
this.userModifiedTopLevelFields.clear();
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
resetRuntimeUserEditedState(): void {
|
|
173
|
+
this.formValueRuntime?.resetUserEditedState?.();
|
|
174
|
+
}
|
|
175
|
+
|
|
169
176
|
public async onDispatchEventStart(eventName: string, options?: any, inputArgs?: Record<string, any>): Promise<void> {
|
|
170
177
|
if (eventName === 'beforeRender') {
|
|
171
178
|
const grid = this.subModels.grid;
|
|
@@ -420,6 +420,23 @@ describe('FormBlockModel (form/formValues injection & server resolve anchors)',
|
|
|
420
420
|
expect(savedUids).toContain('grid-1');
|
|
421
421
|
});
|
|
422
422
|
|
|
423
|
+
it('keeps dirty reset separate from runtime user-edited reset', async () => {
|
|
424
|
+
const model = await setupFormModel();
|
|
425
|
+
const resetUserEditedState = vi.fn();
|
|
426
|
+
model.formValueRuntime = { resetUserEditedState } as any;
|
|
427
|
+
|
|
428
|
+
model.markUserModifiedFields({ status: 'draft' });
|
|
429
|
+
expect(model.getUserModifiedFields().has('status')).toBe(true);
|
|
430
|
+
|
|
431
|
+
model.resetUserModifiedFields();
|
|
432
|
+
|
|
433
|
+
expect(model.getUserModifiedFields().size).toBe(0);
|
|
434
|
+
expect(resetUserEditedState).not.toHaveBeenCalled();
|
|
435
|
+
|
|
436
|
+
model.resetRuntimeUserEditedState();
|
|
437
|
+
expect(resetUserEditedState).toHaveBeenCalledTimes(1);
|
|
438
|
+
});
|
|
439
|
+
|
|
423
440
|
it('re-syncs delegated assignRules when grid submodel is added after block init', async () => {
|
|
424
441
|
const model = await setupFormModel();
|
|
425
442
|
const syncAssignRules = vi.fn();
|