@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.3

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.
Files changed (220) hide show
  1. package/es/PluginSettingsManager.d.ts +33 -0
  2. package/es/RouteRepository.d.ts +21 -6
  3. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  4. package/es/components/category-tabs/index.d.ts +9 -0
  5. package/es/components/form/JsonTextArea.d.ts +2 -1
  6. package/es/components/form/TypedVariableInput.d.ts +22 -4
  7. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  8. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  9. package/es/components/form/filter/index.d.ts +2 -0
  10. package/es/components/form/index.d.ts +1 -0
  11. package/es/components/index.d.ts +2 -0
  12. package/es/flow/actions/afterSuccess.d.ts +8 -1
  13. package/es/flow/actions/index.d.ts +1 -1
  14. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  15. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  16. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
  17. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  18. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  19. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  20. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  21. package/es/flow/components/FieldAssignValueInput.d.ts +2 -0
  22. package/es/flow/index.d.ts +1 -0
  23. package/es/flow/models/base/GridModel.d.ts +1 -1
  24. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  25. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  26. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  27. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  28. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  29. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  30. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  31. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  32. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  33. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  34. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  35. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  36. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  37. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  38. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  39. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  40. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  41. package/es/flow/utils/formValueDeps.d.ts +32 -0
  42. package/es/flow-compat/FieldValidation.d.ts +2 -1
  43. package/es/index.d.ts +2 -1
  44. package/es/index.mjs +233 -137
  45. package/lib/index.js +234 -138
  46. package/package.json +7 -7
  47. package/src/PluginSettingsManager.ts +53 -0
  48. package/src/RouteRepository.ts +126 -24
  49. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  50. package/src/__tests__/RouteRepository.test.ts +216 -0
  51. package/src/__tests__/browserChecker.test.ts +61 -0
  52. package/src/__tests__/exports.test.ts +16 -0
  53. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
  54. package/src/__tests__/plugin-manager.test.tsx +44 -2
  55. package/src/__tests__/settings-center.test.tsx +40 -1
  56. package/src/collection-manager/field-configure.ts +1 -1
  57. package/src/collection-manager/interfaces/id.ts +1 -1
  58. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  59. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  60. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  61. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  62. package/src/collection-manager/interfaces/obo.tsx +2 -2
  63. package/src/collection-manager/interfaces/oho.tsx +2 -2
  64. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  65. package/src/collection-manager/interfaces/uuid.ts +1 -1
  66. package/src/components/AppComponents.tsx +19 -3
  67. package/src/components/README.md +7 -1
  68. package/src/components/README.zh-CN.md +6 -1
  69. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  70. package/src/components/category-tabs/index.ts +10 -0
  71. package/src/components/form/JsonTextArea.tsx +21 -11
  72. package/src/components/form/TypedVariableInput.tsx +416 -93
  73. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  74. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  75. package/src/components/form/__tests__/TypedVariableInput.test.tsx +268 -9
  76. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  77. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  78. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  79. package/src/components/form/filter/index.ts +2 -0
  80. package/src/components/form/index.tsx +1 -0
  81. package/src/components/index.ts +2 -0
  82. package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
  83. package/src/flow/__tests__/getKey.test.ts +7 -0
  84. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  85. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  86. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
  87. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
  88. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  89. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  90. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  91. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  92. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +104 -0
  93. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  94. package/src/flow/actions/afterSuccess.tsx +142 -3
  95. package/src/flow/actions/customVariable.tsx +1 -2
  96. package/src/flow/actions/dataScopeFilter.ts +10 -0
  97. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  98. package/src/flow/actions/index.ts +1 -1
  99. package/src/flow/actions/linkageRules.tsx +123 -35
  100. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  101. package/src/flow/actions/openView.tsx +38 -4
  102. package/src/flow/actions/runjs.tsx +2 -14
  103. package/src/flow/actions/validation.tsx +62 -30
  104. package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
  105. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
  106. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
  107. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +34 -10
  108. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
  109. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
  110. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  111. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  112. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  113. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  114. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  115. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  116. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
  117. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  118. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +82 -0
  119. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
  120. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  121. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  122. package/src/flow/components/DefaultValue.tsx +1 -2
  123. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  124. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  125. package/src/flow/components/FieldAssignValueInput.tsx +10 -5
  126. package/src/flow/components/FlowRoute.tsx +56 -11
  127. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  128. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  129. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  130. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  131. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  132. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  133. package/src/flow/components/fieldAssignOptions.ts +155 -27
  134. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  135. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  136. package/src/flow/index.ts +2 -1
  137. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  138. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  139. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  140. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  141. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  142. package/src/flow/models/base/ActionModel.tsx +12 -1
  143. package/src/flow/models/base/ActionModelCore.tsx +6 -4
  144. package/src/flow/models/base/GridModel.tsx +38 -7
  145. package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
  146. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
  147. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  148. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  149. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  150. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  151. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
  152. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  153. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
  154. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  155. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  156. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  157. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  158. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  159. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  160. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  161. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  162. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  163. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  164. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  165. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  166. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  167. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  168. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  169. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  170. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
  171. package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
  172. package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
  173. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  174. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  175. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  176. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  177. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
  178. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  179. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  180. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  181. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  182. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
  183. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
  184. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  185. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  186. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  187. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  188. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  189. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  190. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  191. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  192. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  193. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  194. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  195. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  196. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  197. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  198. package/src/flow/models/fields/TextareaFieldModel.tsx +49 -3
  199. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  200. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  201. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  202. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +100 -0
  203. package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
  204. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  205. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
  206. package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
  207. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  208. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  209. package/src/flow/utils/formValueDeps.ts +170 -0
  210. package/src/flow-compat/FieldValidation.tsx +122 -60
  211. package/src/flow-compat/Popover.tsx +43 -4
  212. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  213. package/src/index.ts +8 -1
  214. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  215. package/src/nocobase-buildin-plugin/index.tsx +44 -14
  216. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  217. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  218. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  219. package/src/settings-center/plugin-manager/index.tsx +3 -0
  220. package/src/settings-center/utils.tsx +0 -6
@@ -93,4 +93,1026 @@ describe('ensureFormValueDrivenDataScopeClear', () => {
93
93
 
94
94
  expect(onChange).not.toHaveBeenCalled();
95
95
  });
96
+
97
+ it('maps ctx.item.value dependencies to the current row path', () => {
98
+ const emitter = new EventEmitter();
99
+ const formBlock = {
100
+ uid: 'form-1',
101
+ disposed: false,
102
+ emitter,
103
+ context: { form: {} },
104
+ };
105
+
106
+ const onChange = vi.fn();
107
+ const model: any = {
108
+ disposed: false,
109
+ props: {
110
+ value: { id: 1 },
111
+ onChange,
112
+ },
113
+ context: {
114
+ blockModel: formBlock,
115
+ fieldIndex: ['users:1'],
116
+ },
117
+ };
118
+
119
+ const ctx: any = {
120
+ model,
121
+ flowKey: 'selectSettings',
122
+ };
123
+
124
+ const filter = {
125
+ logic: '$and',
126
+ items: [{ path: 'nickname', operator: '$eq', value: '{{ ctx.item.value.nickname }}' }],
127
+ };
128
+
129
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
130
+
131
+ emitter.emit('formValuesChange', {
132
+ changedPaths: [['users', 0, 'nickname']],
133
+ });
134
+ expect(onChange).not.toHaveBeenCalled();
135
+
136
+ emitter.emit('formValuesChange', {
137
+ changedPaths: [['users', 1, 'nickname']],
138
+ });
139
+ expect(onChange).toHaveBeenCalledWith(null);
140
+ });
141
+
142
+ it('parses object-patch changedPaths before matching ctx.item.value dependencies', () => {
143
+ const emitter = new EventEmitter();
144
+ const formBlock = {
145
+ uid: 'form-1',
146
+ disposed: false,
147
+ emitter,
148
+ context: { form: {} },
149
+ };
150
+
151
+ const onChange = vi.fn();
152
+ const model: any = {
153
+ disposed: false,
154
+ props: {
155
+ value: { id: 1 },
156
+ onChange,
157
+ },
158
+ context: {
159
+ blockModel: formBlock,
160
+ fieldIndex: ['users:1'],
161
+ },
162
+ };
163
+
164
+ const ctx: any = {
165
+ model,
166
+ flowKey: 'selectSettings',
167
+ };
168
+
169
+ const filter = {
170
+ logic: '$and',
171
+ items: [{ path: 'nickname', operator: '$eq', value: '{{ ctx.item.value.nickname }}' }],
172
+ };
173
+
174
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
175
+
176
+ emitter.emit('formValuesChange', {
177
+ changedPaths: [['users[1].nickname']],
178
+ });
179
+
180
+ expect(onChange).toHaveBeenCalledWith(null);
181
+ });
182
+
183
+ it('maps root parentItem.value dependencies to the root form path', () => {
184
+ const emitter = new EventEmitter();
185
+ const formBlock = {
186
+ uid: 'form-1',
187
+ disposed: false,
188
+ emitter,
189
+ context: { form: {} },
190
+ };
191
+
192
+ const onChange = vi.fn();
193
+ const model: any = {
194
+ disposed: false,
195
+ props: {
196
+ value: { id: 1 },
197
+ onChange,
198
+ },
199
+ context: {
200
+ blockModel: formBlock,
201
+ fieldIndex: ['roles:0'],
202
+ },
203
+ };
204
+
205
+ const ctx: any = {
206
+ model,
207
+ flowKey: 'selectSettings',
208
+ };
209
+
210
+ const filter = {
211
+ logic: '$and',
212
+ items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.item.parentItem.value.departmentId }}' }],
213
+ };
214
+
215
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
216
+
217
+ emitter.emit('formValuesChange', {
218
+ changedPaths: [['roles', 0, 'name']],
219
+ });
220
+ expect(onChange).not.toHaveBeenCalled();
221
+
222
+ emitter.emit('formValuesChange', {
223
+ changedPaths: [['departmentId']],
224
+ });
225
+ expect(onChange).toHaveBeenCalledWith(null);
226
+ });
227
+
228
+ it('does not clear a row field when a sibling field changes but the item dependency value is unchanged', () => {
229
+ const emitter = new EventEmitter();
230
+ const formBlock = {
231
+ uid: 'form-1',
232
+ disposed: false,
233
+ emitter,
234
+ context: {
235
+ form: {},
236
+ formValues: {
237
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 }, staff_m2o: null }],
238
+ },
239
+ },
240
+ };
241
+
242
+ const onChange = vi.fn();
243
+ const model: any = {
244
+ disposed: false,
245
+ props: {
246
+ value: { id: 1 },
247
+ onChange,
248
+ },
249
+ context: {
250
+ blockModel: formBlock,
251
+ fieldIndex: ['org_o2m:0'],
252
+ },
253
+ };
254
+
255
+ const ctx: any = {
256
+ model,
257
+ flowKey: 'selectSettings',
258
+ };
259
+
260
+ const filter = {
261
+ logic: '$and',
262
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
263
+ };
264
+
265
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
266
+
267
+ emitter.emit('formValuesChange', {
268
+ changedPaths: [['org_o2m', 0]],
269
+ allValues: {
270
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 }, staff_m2o: { id: 10 } }],
271
+ },
272
+ });
273
+
274
+ expect(onChange).not.toHaveBeenCalled();
275
+ });
276
+
277
+ it('clears a row field when a coarse row change includes an actual item dependency value change', () => {
278
+ const emitter = new EventEmitter();
279
+ const formBlock = {
280
+ uid: 'form-1',
281
+ disposed: false,
282
+ emitter,
283
+ context: {
284
+ form: {},
285
+ formValues: {
286
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 } }],
287
+ },
288
+ },
289
+ };
290
+
291
+ const onChange = vi.fn();
292
+ const model: any = {
293
+ disposed: false,
294
+ props: {
295
+ value: { id: 1 },
296
+ onChange,
297
+ },
298
+ context: {
299
+ blockModel: formBlock,
300
+ fieldIndex: ['org_o2m:0'],
301
+ },
302
+ };
303
+
304
+ const ctx: any = {
305
+ model,
306
+ flowKey: 'selectSettings',
307
+ };
308
+
309
+ const filter = {
310
+ logic: '$and',
311
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
312
+ };
313
+
314
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
315
+
316
+ emitter.emit('formValuesChange', {
317
+ changedPaths: [['org_o2m', 0]],
318
+ allValues: {
319
+ org_o2m: [{ companyName: 'b', user_m2o: { id: 1 } }],
320
+ },
321
+ });
322
+
323
+ expect(onChange).toHaveBeenCalledWith(null);
324
+ });
325
+
326
+ it('clears a dependent row field when the sibling relation used by data scope changes', () => {
327
+ const emitter = new EventEmitter();
328
+ const formBlock = {
329
+ uid: 'form-1',
330
+ disposed: false,
331
+ emitter,
332
+ context: {
333
+ form: {},
334
+ formValues: {
335
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 1 }, staff_m2o: { id: 10 } }],
336
+ },
337
+ },
338
+ };
339
+
340
+ const onChange = vi.fn();
341
+ const model: any = {
342
+ disposed: false,
343
+ props: {
344
+ value: { id: 10 },
345
+ onChange,
346
+ },
347
+ context: {
348
+ blockModel: formBlock,
349
+ fieldIndex: ['org_o2m:0'],
350
+ },
351
+ };
352
+
353
+ const ctx: any = {
354
+ model,
355
+ flowKey: 'selectSettings',
356
+ };
357
+
358
+ const filter = {
359
+ logic: '$and',
360
+ items: [{ path: 'id', operator: '$eq', value: '{{ ctx.item.value.user_m2o.id }}' }],
361
+ };
362
+
363
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
364
+
365
+ emitter.emit('formValuesChange', {
366
+ changedPaths: [['org_o2m', 0]],
367
+ allValues: {
368
+ org_o2m: [{ companyName: 'a', user_m2o: { id: 2 }, staff_m2o: { id: 10 } }],
369
+ },
370
+ });
371
+
372
+ expect(onChange).toHaveBeenCalledWith(null);
373
+ });
374
+
375
+ it('does not clear a row field when another row is removed after it', () => {
376
+ const emitter = new EventEmitter();
377
+ const formBlock = {
378
+ uid: 'form-1',
379
+ disposed: false,
380
+ emitter,
381
+ context: {
382
+ form: {},
383
+ formValues: {
384
+ org_o2m: [
385
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
386
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
387
+ ],
388
+ },
389
+ },
390
+ };
391
+
392
+ const onChange = vi.fn();
393
+ const model: any = {
394
+ disposed: false,
395
+ props: {
396
+ value: { id: 1 },
397
+ onChange,
398
+ },
399
+ context: {
400
+ blockModel: formBlock,
401
+ fieldIndex: ['org_o2m:0'],
402
+ },
403
+ };
404
+
405
+ const ctx: any = {
406
+ model,
407
+ flowKey: 'selectSettings',
408
+ };
409
+
410
+ const filter = {
411
+ logic: '$and',
412
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
413
+ };
414
+
415
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
416
+
417
+ emitter.emit('formValuesChange', {
418
+ changedPaths: [['org_o2m']],
419
+ allValues: {
420
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
421
+ },
422
+ });
423
+
424
+ expect(onChange).not.toHaveBeenCalled();
425
+ });
426
+
427
+ it('does not clear a row field when another row is removed before it and the row index changes', () => {
428
+ const emitter = new EventEmitter();
429
+ const formBlock = {
430
+ uid: 'form-1',
431
+ disposed: false,
432
+ emitter,
433
+ context: {
434
+ form: {},
435
+ formValues: {
436
+ org_o2m: [
437
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
438
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
439
+ ],
440
+ },
441
+ },
442
+ };
443
+
444
+ const onChange = vi.fn();
445
+ const model: any = {
446
+ disposed: false,
447
+ props: {
448
+ value: { id: 2 },
449
+ onChange,
450
+ },
451
+ context: {
452
+ blockModel: formBlock,
453
+ fieldIndex: ['org_o2m:1'],
454
+ },
455
+ };
456
+
457
+ const ctx: any = {
458
+ model,
459
+ flowKey: 'selectSettings',
460
+ };
461
+
462
+ const filter = {
463
+ logic: '$and',
464
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
465
+ };
466
+
467
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
468
+
469
+ emitter.emit('formValuesChange', {
470
+ changedPaths: [['org_o2m']],
471
+ allValues: {
472
+ org_o2m: [{ __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } }],
473
+ },
474
+ });
475
+
476
+ expect(onChange).not.toHaveBeenCalled();
477
+ });
478
+
479
+ it('does not clear a row field after reindexing when rows use a custom filter target key', () => {
480
+ const emitter = new EventEmitter();
481
+ const usersField = {
482
+ targetCollection: { filterTargetKey: 'name' },
483
+ isAssociationField: () => true,
484
+ };
485
+ const formBlock = {
486
+ uid: 'form-1',
487
+ disposed: false,
488
+ emitter,
489
+ context: {
490
+ form: {},
491
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
492
+ formValues: {
493
+ org_o2m: [
494
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
495
+ { name: 'bob', companyName: 'b', user_m2o: { id: 2 } },
496
+ ],
497
+ },
498
+ },
499
+ };
500
+
501
+ const onChange = vi.fn();
502
+ const model: any = {
503
+ disposed: false,
504
+ props: {
505
+ value: { id: 2 },
506
+ onChange,
507
+ },
508
+ context: {
509
+ blockModel: formBlock,
510
+ fieldIndex: ['org_o2m:1'],
511
+ },
512
+ };
513
+
514
+ const ctx: any = {
515
+ model,
516
+ flowKey: 'selectSettings',
517
+ };
518
+
519
+ const filter = {
520
+ logic: '$and',
521
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
522
+ };
523
+
524
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
525
+
526
+ emitter.emit('formValuesChange', {
527
+ changedPaths: [['org_o2m']],
528
+ allValues: {
529
+ org_o2m: [{ name: 'bob', companyName: 'b', user_m2o: { id: 2 } }],
530
+ },
531
+ });
532
+
533
+ expect(onChange).not.toHaveBeenCalled();
534
+ });
535
+
536
+ it('does not clear a row field after reindexing when rows use a composite filter target key', () => {
537
+ const emitter = new EventEmitter();
538
+ const usersField = {
539
+ targetCollection: { filterTargetKey: ['tenantId', 'code'] },
540
+ isAssociationField: () => true,
541
+ };
542
+ const formBlock = {
543
+ uid: 'form-1',
544
+ disposed: false,
545
+ emitter,
546
+ context: {
547
+ form: {},
548
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
549
+ formValues: {
550
+ org_o2m: [
551
+ { tenantId: 1, code: 'a', companyName: 'a', user_m2o: { id: 1 } },
552
+ { tenantId: 1, code: 'b', companyName: 'b', user_m2o: { id: 2 } },
553
+ ],
554
+ },
555
+ },
556
+ };
557
+
558
+ const onChange = vi.fn();
559
+ const model: any = {
560
+ disposed: false,
561
+ props: {
562
+ value: { id: 2 },
563
+ onChange,
564
+ },
565
+ context: {
566
+ blockModel: formBlock,
567
+ fieldIndex: ['org_o2m:1'],
568
+ },
569
+ };
570
+
571
+ const ctx: any = {
572
+ model,
573
+ flowKey: 'selectSettings',
574
+ };
575
+
576
+ const filter = {
577
+ logic: '$and',
578
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
579
+ };
580
+
581
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
582
+
583
+ emitter.emit('formValuesChange', {
584
+ changedPaths: [['org_o2m']],
585
+ allValues: {
586
+ org_o2m: [{ tenantId: 1, code: 'b', companyName: 'b', user_m2o: { id: 2 } }],
587
+ },
588
+ });
589
+
590
+ expect(onChange).not.toHaveBeenCalled();
591
+ });
592
+
593
+ it('keeps tracking a row after its custom filter target key changes', () => {
594
+ const emitter = new EventEmitter();
595
+ const usersField = {
596
+ targetCollection: { filterTargetKey: 'name' },
597
+ isAssociationField: () => true,
598
+ };
599
+ const formBlock = {
600
+ uid: 'form-1',
601
+ disposed: false,
602
+ emitter,
603
+ context: {
604
+ form: {},
605
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
606
+ formValues: {
607
+ org_o2m: [
608
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
609
+ { name: 'bob', companyName: 'b', user_m2o: { id: 2 } },
610
+ ],
611
+ },
612
+ },
613
+ };
614
+
615
+ const onChange = vi.fn();
616
+ const model: any = {
617
+ disposed: false,
618
+ props: {
619
+ value: { id: 2 },
620
+ onChange,
621
+ },
622
+ context: {
623
+ blockModel: formBlock,
624
+ fieldIndex: ['org_o2m:1'],
625
+ },
626
+ };
627
+
628
+ const ctx: any = {
629
+ model,
630
+ flowKey: 'selectSettings',
631
+ };
632
+
633
+ const filter = {
634
+ logic: '$and',
635
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
636
+ };
637
+
638
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
639
+
640
+ emitter.emit('formValuesChange', {
641
+ changedPaths: [['org_o2m', 1, 'name']],
642
+ allValues: {
643
+ org_o2m: [
644
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
645
+ { name: 'robert', companyName: 'b', user_m2o: { id: 2 } },
646
+ ],
647
+ },
648
+ });
649
+
650
+ expect(onChange).not.toHaveBeenCalled();
651
+
652
+ emitter.emit('formValuesChange', {
653
+ changedPaths: [['org_o2m', 1, 'companyName']],
654
+ allValues: {
655
+ org_o2m: [
656
+ { name: 'alice', companyName: 'a', user_m2o: { id: 1 } },
657
+ { name: 'robert', companyName: 'c', user_m2o: { id: 2 } },
658
+ ],
659
+ },
660
+ });
661
+
662
+ expect(onChange).toHaveBeenCalledWith(null);
663
+ });
664
+
665
+ it('keeps tracking a row after its temporary identity changes to a persisted key', () => {
666
+ const emitter = new EventEmitter();
667
+ const usersField = {
668
+ targetCollection: { filterTargetKey: 'id' },
669
+ isAssociationField: () => true,
670
+ };
671
+ const formBlock = {
672
+ uid: 'form-1',
673
+ disposed: false,
674
+ emitter,
675
+ context: {
676
+ form: {},
677
+ collection: { getField: (name: string) => (name === 'org_o2m' ? usersField : null) },
678
+ formValues: {
679
+ org_o2m: [
680
+ { id: 1, companyName: 'a', user_m2o: { id: 1 } },
681
+ { __index__: 'row-b', __is_new__: true, companyName: 'b', user_m2o: { id: 2 } },
682
+ ],
683
+ },
684
+ },
685
+ };
686
+
687
+ const onChange = vi.fn();
688
+ const model: any = {
689
+ disposed: false,
690
+ props: {
691
+ value: { id: 2 },
692
+ onChange,
693
+ },
694
+ context: {
695
+ blockModel: formBlock,
696
+ fieldIndex: ['org_o2m:1'],
697
+ },
698
+ };
699
+
700
+ const ctx: any = {
701
+ model,
702
+ flowKey: 'selectSettings',
703
+ };
704
+
705
+ const filter = {
706
+ logic: '$and',
707
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
708
+ };
709
+
710
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
711
+
712
+ emitter.emit('formValuesChange', {
713
+ changedPaths: [['org_o2m', 1, 'id']],
714
+ allValues: {
715
+ org_o2m: [
716
+ { id: 1, companyName: 'a', user_m2o: { id: 1 } },
717
+ { __index__: 'row-b', id: 2, companyName: 'b', user_m2o: { id: 2 } },
718
+ ],
719
+ },
720
+ });
721
+
722
+ expect(onChange).not.toHaveBeenCalled();
723
+
724
+ emitter.emit('formValuesChange', {
725
+ changedPaths: [['org_o2m', 1, 'companyName']],
726
+ allValues: {
727
+ org_o2m: [
728
+ { id: 1, companyName: 'a', user_m2o: { id: 1 } },
729
+ { __index__: 'row-b', id: 2, companyName: 'c', user_m2o: { id: 2 } },
730
+ ],
731
+ },
732
+ });
733
+
734
+ expect(onChange).toHaveBeenCalledWith(null);
735
+ });
736
+
737
+ it('does not clear a stale row field binding when its row has been removed', () => {
738
+ const emitter = new EventEmitter();
739
+ const formBlock = {
740
+ uid: 'form-1',
741
+ disposed: false,
742
+ emitter,
743
+ context: {
744
+ form: {},
745
+ formValues: {
746
+ org_o2m: [
747
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
748
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
749
+ ],
750
+ },
751
+ },
752
+ };
753
+
754
+ const onChange = vi.fn();
755
+ const model: any = {
756
+ disposed: false,
757
+ props: {
758
+ value: { id: 2 },
759
+ onChange,
760
+ },
761
+ context: {
762
+ blockModel: formBlock,
763
+ fieldIndex: ['org_o2m:1'],
764
+ },
765
+ };
766
+
767
+ const ctx: any = {
768
+ model,
769
+ flowKey: 'selectSettings',
770
+ };
771
+
772
+ const filter = {
773
+ logic: '$and',
774
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
775
+ };
776
+
777
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
778
+
779
+ emitter.emit('formValuesChange', {
780
+ changedPaths: [['org_o2m']],
781
+ allValues: {
782
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
783
+ },
784
+ });
785
+
786
+ expect(onChange).not.toHaveBeenCalled();
787
+ });
788
+
789
+ it('does not reattach a stale row field binding to the next row after deletion', () => {
790
+ const emitter = new EventEmitter();
791
+ const formBlock = {
792
+ uid: 'form-1',
793
+ disposed: false,
794
+ emitter,
795
+ context: {
796
+ form: {},
797
+ formValues: {
798
+ org_o2m: [
799
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
800
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
801
+ { __index__: 'row-c', companyName: 'c', user_m2o: { id: 3 } },
802
+ ],
803
+ },
804
+ },
805
+ };
806
+
807
+ const onChange = vi.fn();
808
+ const model: any = {
809
+ disposed: false,
810
+ props: {
811
+ value: { id: 2 },
812
+ onChange,
813
+ },
814
+ context: {
815
+ blockModel: formBlock,
816
+ fieldIndex: ['org_o2m:1'],
817
+ },
818
+ };
819
+
820
+ const ctx: any = {
821
+ model,
822
+ flowKey: 'selectSettings',
823
+ };
824
+
825
+ const filter = {
826
+ logic: '$and',
827
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
828
+ };
829
+
830
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
831
+
832
+ emitter.emit('formValuesChange', {
833
+ changedPaths: [['org_o2m']],
834
+ allValues: {
835
+ org_o2m: [
836
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
837
+ { __index__: 'row-c', companyName: 'c', user_m2o: { id: 3 } },
838
+ ],
839
+ },
840
+ });
841
+
842
+ emitter.emit('formValuesChange', {
843
+ changedPaths: [['org_o2m', 1, 'companyName']],
844
+ allValues: {
845
+ org_o2m: [
846
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
847
+ { __index__: 'row-c', companyName: 'changed', user_m2o: { id: 3 } },
848
+ ],
849
+ },
850
+ });
851
+
852
+ expect(onChange).not.toHaveBeenCalled();
853
+ });
854
+
855
+ it('does not reattach a stale row field binding when the first stale event is an old index child change', () => {
856
+ const emitter = new EventEmitter();
857
+ const formBlock = {
858
+ uid: 'form-1',
859
+ disposed: false,
860
+ emitter,
861
+ context: {
862
+ form: {},
863
+ formValues: {
864
+ org_o2m: [
865
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
866
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
867
+ { __index__: 'row-c', companyName: 'c', user_m2o: { id: 3 } },
868
+ ],
869
+ },
870
+ },
871
+ };
872
+
873
+ const onChange = vi.fn();
874
+ const model: any = {
875
+ disposed: false,
876
+ props: {
877
+ value: { id: 2 },
878
+ onChange,
879
+ },
880
+ context: {
881
+ blockModel: formBlock,
882
+ fieldIndex: ['org_o2m:1'],
883
+ },
884
+ };
885
+
886
+ const ctx: any = {
887
+ model,
888
+ flowKey: 'selectSettings',
889
+ };
890
+
891
+ const filter = {
892
+ logic: '$and',
893
+ items: [{ path: 'name', operator: '$includes', value: '{{ ctx.item.value.companyName }}' }],
894
+ };
895
+
896
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
897
+
898
+ emitter.emit('formValuesChange', {
899
+ changedPaths: [['org_o2m', 1, 'companyName']],
900
+ allValues: {
901
+ org_o2m: [
902
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
903
+ { __index__: 'row-c', companyName: 'changed', user_m2o: { id: 3 } },
904
+ ],
905
+ },
906
+ });
907
+
908
+ expect(onChange).not.toHaveBeenCalled();
909
+ });
910
+
911
+ it('does not clear a stale removed row binding when data scope depends on the item length', () => {
912
+ const emitter = new EventEmitter();
913
+ const formBlock = {
914
+ uid: 'form-1',
915
+ disposed: false,
916
+ emitter,
917
+ context: {
918
+ form: {},
919
+ formValues: {
920
+ org_o2m: [
921
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
922
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
923
+ ],
924
+ },
925
+ },
926
+ };
927
+
928
+ const onChange = vi.fn();
929
+ const model: any = {
930
+ disposed: false,
931
+ props: {
932
+ value: { id: 2 },
933
+ onChange,
934
+ },
935
+ context: {
936
+ blockModel: formBlock,
937
+ fieldIndex: ['org_o2m:1'],
938
+ },
939
+ };
940
+
941
+ const ctx: any = {
942
+ model,
943
+ flowKey: 'selectSettings',
944
+ };
945
+
946
+ const filter = {
947
+ logic: '$and',
948
+ items: [{ path: 'count', operator: '$eq', value: '{{ ctx.item.length }}' }],
949
+ };
950
+
951
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
952
+
953
+ emitter.emit('formValuesChange', {
954
+ changedPaths: [['org_o2m']],
955
+ allValues: {
956
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
957
+ },
958
+ });
959
+
960
+ expect(onChange).not.toHaveBeenCalled();
961
+ });
962
+
963
+ it('still clears a row field when data scope explicitly depends on the item length', () => {
964
+ const emitter = new EventEmitter();
965
+ const formBlock = {
966
+ uid: 'form-1',
967
+ disposed: false,
968
+ emitter,
969
+ context: {
970
+ form: {},
971
+ formValues: {
972
+ org_o2m: [
973
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
974
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
975
+ ],
976
+ },
977
+ },
978
+ };
979
+
980
+ const onChange = vi.fn();
981
+ const model: any = {
982
+ disposed: false,
983
+ props: {
984
+ value: { id: 1 },
985
+ onChange,
986
+ },
987
+ context: {
988
+ blockModel: formBlock,
989
+ fieldIndex: ['org_o2m:0'],
990
+ },
991
+ };
992
+
993
+ const ctx: any = {
994
+ model,
995
+ flowKey: 'selectSettings',
996
+ };
997
+
998
+ const filter = {
999
+ logic: '$and',
1000
+ items: [{ path: 'count', operator: '$eq', value: '{{ ctx.item.length }}' }],
1001
+ };
1002
+
1003
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1004
+
1005
+ emitter.emit('formValuesChange', {
1006
+ changedPaths: [['org_o2m']],
1007
+ allValues: {
1008
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
1009
+ },
1010
+ });
1011
+
1012
+ expect(onChange).toHaveBeenCalledWith(null);
1013
+ });
1014
+
1015
+ it('does not clear a row field when data scope depends on the item index and another row is removed after it', () => {
1016
+ const emitter = new EventEmitter();
1017
+ const formBlock = {
1018
+ uid: 'form-1',
1019
+ disposed: false,
1020
+ emitter,
1021
+ context: {
1022
+ form: {},
1023
+ formValues: {
1024
+ org_o2m: [
1025
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1026
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
1027
+ ],
1028
+ },
1029
+ },
1030
+ };
1031
+
1032
+ const onChange = vi.fn();
1033
+ const model: any = {
1034
+ disposed: false,
1035
+ props: {
1036
+ value: { id: 1 },
1037
+ onChange,
1038
+ },
1039
+ context: {
1040
+ blockModel: formBlock,
1041
+ fieldIndex: ['org_o2m:0'],
1042
+ },
1043
+ };
1044
+
1045
+ const ctx: any = {
1046
+ model,
1047
+ flowKey: 'selectSettings',
1048
+ };
1049
+
1050
+ const filter = {
1051
+ logic: '$and',
1052
+ items: [{ path: 'index', operator: '$eq', value: '{{ ctx.item.index }}' }],
1053
+ };
1054
+
1055
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1056
+
1057
+ emitter.emit('formValuesChange', {
1058
+ changedPaths: [['org_o2m']],
1059
+ allValues: {
1060
+ org_o2m: [{ __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } }],
1061
+ },
1062
+ });
1063
+
1064
+ expect(onChange).not.toHaveBeenCalled();
1065
+ });
1066
+
1067
+ it('clears a row field when data scope depends on the item index and the row index changes', () => {
1068
+ const emitter = new EventEmitter();
1069
+ const formBlock = {
1070
+ uid: 'form-1',
1071
+ disposed: false,
1072
+ emitter,
1073
+ context: {
1074
+ form: {},
1075
+ formValues: {
1076
+ org_o2m: [
1077
+ { __index__: 'row-a', companyName: 'a', user_m2o: { id: 1 } },
1078
+ { __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } },
1079
+ ],
1080
+ },
1081
+ },
1082
+ };
1083
+
1084
+ const onChange = vi.fn();
1085
+ const model: any = {
1086
+ disposed: false,
1087
+ props: {
1088
+ value: { id: 2 },
1089
+ onChange,
1090
+ },
1091
+ context: {
1092
+ blockModel: formBlock,
1093
+ fieldIndex: ['org_o2m:1'],
1094
+ },
1095
+ };
1096
+
1097
+ const ctx: any = {
1098
+ model,
1099
+ flowKey: 'selectSettings',
1100
+ };
1101
+
1102
+ const filter = {
1103
+ logic: '$and',
1104
+ items: [{ path: 'index', operator: '$eq', value: '{{ ctx.item.index }}' }],
1105
+ };
1106
+
1107
+ ensureFormValueDrivenDataScopeClear(ctx, filter);
1108
+
1109
+ emitter.emit('formValuesChange', {
1110
+ changedPaths: [['org_o2m']],
1111
+ allValues: {
1112
+ org_o2m: [{ __index__: 'row-b', companyName: 'b', user_m2o: { id: 2 } }],
1113
+ },
1114
+ });
1115
+
1116
+ expect(onChange).toHaveBeenCalledWith(null);
1117
+ });
96
1118
  });