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

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 (334) hide show
  1. package/es/APIClient.d.ts +1 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/PluginSettingsManager.d.ts +33 -0
  4. package/es/RouteRepository.d.ts +29 -6
  5. package/es/RouterManager.d.ts +15 -0
  6. package/es/authRedirect.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  8. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  9. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  10. package/es/components/category-tabs/index.d.ts +9 -0
  11. package/es/components/form/JsonTextArea.d.ts +2 -1
  12. package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
  13. package/es/components/form/TypedVariableInput.d.ts +31 -5
  14. package/es/components/form/VariableJsonTextArea.d.ts +19 -0
  15. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  16. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  17. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  18. package/es/components/form/filter/index.d.ts +2 -0
  19. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  20. package/es/components/form/index.d.ts +1 -0
  21. package/es/components/index.d.ts +2 -0
  22. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  23. package/es/entry-actions/index.d.ts +9 -0
  24. package/es/flow/actions/afterSuccess.d.ts +8 -1
  25. package/es/flow/actions/index.d.ts +2 -2
  26. package/es/flow/actions/linkageRules.d.ts +2 -0
  27. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  28. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  29. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
  30. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
  31. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +3 -0
  32. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
  33. package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
  34. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  35. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  36. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  37. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
  38. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  39. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  40. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  41. package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
  42. package/es/flow/components/FlowRoute.d.ts +2 -2
  43. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  44. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  45. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  46. package/es/flow/index.d.ts +1 -0
  47. package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
  48. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  49. package/es/flow/models/base/BlockGridModel.d.ts +3 -0
  50. package/es/flow/models/base/GridModel.d.ts +1 -1
  51. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  52. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  53. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  54. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
  55. package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
  56. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  57. package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
  58. package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
  59. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
  60. package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
  61. package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
  62. package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
  63. package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
  64. package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
  65. package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
  66. package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
  67. package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
  68. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  69. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
  70. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
  71. package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
  72. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  73. package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
  74. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  75. package/es/flow/utils/formValueDeps.d.ts +32 -0
  76. package/es/flow-compat/FieldValidation.d.ts +2 -1
  77. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  78. package/es/flow-compat/routeTypes.d.ts +1 -0
  79. package/es/index.d.ts +8 -1
  80. package/es/index.mjs +367 -181
  81. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  82. package/es/utils/index.d.ts +1 -0
  83. package/es/utils/markdownSanitize.d.ts +11 -0
  84. package/lib/index.js +367 -181
  85. package/lib/locale/languageCodes.js +3 -1
  86. package/package.json +9 -7
  87. package/src/APIClient.ts +1 -10
  88. package/src/Application.tsx +4 -0
  89. package/src/BaseApplication.tsx +13 -6
  90. package/src/PluginSettingsManager.ts +53 -0
  91. package/src/RouteRepository.ts +151 -24
  92. package/src/RouterManager.tsx +142 -1
  93. package/src/__tests__/PluginSettingsManager.test.ts +13 -0
  94. package/src/__tests__/RouteRepository.test.ts +239 -0
  95. package/src/__tests__/RouterManager.test.ts +125 -0
  96. package/src/__tests__/app.test.tsx +73 -0
  97. package/src/__tests__/authRedirect.test.ts +17 -0
  98. package/src/__tests__/browserChecker.test.ts +61 -0
  99. package/src/__tests__/exports.test.ts +16 -0
  100. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  101. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +234 -2
  102. package/src/__tests__/plugin-manager.test.tsx +44 -2
  103. package/src/__tests__/settings-center.test.tsx +40 -1
  104. package/src/authRedirect.ts +38 -0
  105. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  106. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  107. package/src/collection-manager/field-configure.ts +1 -1
  108. package/src/collection-manager/field-validation.ts +1 -1
  109. package/src/collection-manager/interfaces/id.ts +1 -1
  110. package/src/collection-manager/interfaces/m2m.tsx +2 -2
  111. package/src/collection-manager/interfaces/m2o.tsx +2 -2
  112. package/src/collection-manager/interfaces/nanoid.ts +1 -1
  113. package/src/collection-manager/interfaces/o2m.tsx +2 -2
  114. package/src/collection-manager/interfaces/obo.tsx +2 -2
  115. package/src/collection-manager/interfaces/oho.tsx +2 -2
  116. package/src/collection-manager/interfaces/properties/index.ts +2 -2
  117. package/src/collection-manager/interfaces/uuid.ts +1 -1
  118. package/src/components/AppComponents.tsx +19 -3
  119. package/src/components/README.md +7 -1
  120. package/src/components/README.zh-CN.md +6 -1
  121. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  122. package/src/components/category-tabs/index.ts +10 -0
  123. package/src/components/form/JsonTextArea.tsx +25 -11
  124. package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
  125. package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
  126. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
  127. package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
  128. package/src/components/form/TypedVariableInput.tsx +452 -101
  129. package/src/components/form/VariableJsonTextArea.tsx +175 -0
  130. package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
  131. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  132. package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
  133. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  134. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  135. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  136. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  137. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  138. package/src/components/form/filter/index.ts +2 -0
  139. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  140. package/src/components/form/index.tsx +1 -0
  141. package/src/components/index.ts +2 -0
  142. package/src/entry-actions/EntryActionManager.ts +76 -0
  143. package/src/entry-actions/index.ts +10 -0
  144. package/src/flow/FlowPage.tsx +38 -3
  145. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  146. package/src/flow/__tests__/FlowRoute.test.tsx +1181 -80
  147. package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
  148. package/src/flow/__tests__/getKey.test.ts +7 -0
  149. package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
  150. package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +115 -0
  151. package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
  152. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
  153. package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1124 -0
  154. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
  155. package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
  156. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
  157. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
  158. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  159. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  160. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
  161. package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
  162. package/src/flow/actions/afterSuccess.tsx +142 -3
  163. package/src/flow/actions/customVariable.tsx +1 -2
  164. package/src/flow/actions/dataScopeFilter.ts +20 -1
  165. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  166. package/src/flow/actions/index.ts +3 -1
  167. package/src/flow/actions/linkageRules.tsx +209 -47
  168. package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
  169. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  170. package/src/flow/actions/openView.tsx +59 -5
  171. package/src/flow/actions/runjs.tsx +2 -14
  172. package/src/flow/actions/validation.tsx +62 -30
  173. package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
  174. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
  175. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
  176. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -14
  177. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
  178. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
  179. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
  180. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
  181. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
  182. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
  183. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
  184. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  185. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  186. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  187. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +435 -0
  188. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
  189. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
  190. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
  191. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  192. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
  193. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
  194. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  195. package/src/flow/common/Markdown/Display.tsx +14 -3
  196. package/src/flow/common/Markdown/Edit.tsx +19 -7
  197. package/src/flow/common/Markdown/style.ts +4 -0
  198. package/src/flow/components/BlockItemCard.tsx +2 -2
  199. package/src/flow/components/DefaultValue.tsx +1 -2
  200. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  201. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  202. package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
  203. package/src/flow/components/FieldAssignValueInput.tsx +91 -20
  204. package/src/flow/components/FlowRoute.tsx +256 -53
  205. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  206. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  207. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
  208. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  209. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
  210. package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
  211. package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
  212. package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
  213. package/src/flow/components/fieldAssignOptions.ts +155 -27
  214. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  215. package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
  216. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  217. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  218. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
  219. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  220. package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
  221. package/src/flow/index.ts +4 -1
  222. package/src/flow/internal/components/Markdown/util.ts +2 -1
  223. package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
  224. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  225. package/src/flow/models/actions/JSActionModel.tsx +2 -7
  226. package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
  227. package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
  228. package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
  229. package/src/flow/models/base/ActionModel.tsx +21 -8
  230. package/src/flow/models/base/ActionModelCore.tsx +11 -4
  231. package/src/flow/models/base/BlockGridModel.tsx +26 -0
  232. package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
  233. package/src/flow/models/base/GridModel.tsx +38 -7
  234. package/src/flow/models/base/PageModel/PageModel.tsx +387 -50
  235. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  236. package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
  237. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +817 -13
  238. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  239. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  240. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
  241. package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
  242. package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
  243. package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
  244. package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
  245. package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
  246. package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
  247. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +59 -6
  248. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  249. package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
  250. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  251. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +143 -4
  252. package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
  253. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  254. package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
  255. package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
  256. package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
  257. package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
  258. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  259. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  260. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  261. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
  262. package/src/flow/models/blocks/form/submitHandler.ts +17 -3
  263. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
  264. package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
  265. package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
  266. package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
  267. package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
  268. package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
  269. package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
  270. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +38 -14
  271. package/src/flow/models/blocks/table/TableBlockModel.tsx +42 -19
  272. package/src/flow/models/blocks/table/TableColumnModel.tsx +72 -6
  273. package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
  274. package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
  275. package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
  276. package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
  277. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
  278. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +243 -1
  279. package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
  280. package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
  281. package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
  282. package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
  283. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +31 -7
  284. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
  285. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
  286. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
  287. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
  288. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
  289. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
  290. package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
  291. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  292. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  293. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
  294. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
  295. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  296. package/src/flow/models/fields/InputFieldModel.tsx +40 -2
  297. package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
  298. package/src/flow/models/fields/JSFieldModel.tsx +2 -7
  299. package/src/flow/models/fields/JSItemModel.tsx +2 -14
  300. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  301. package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
  302. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  303. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  304. package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
  305. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  306. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
  307. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  308. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  309. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  310. package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
  311. package/src/flow/resolveViewParamsToViewList.tsx +11 -3
  312. package/src/flow/routeTransientInputArgs.ts +27 -0
  313. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
  314. package/src/flow/utils/dataScopeFormValueClear.ts +219 -81
  315. package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
  316. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
  317. package/src/flow/utils/formValueDeps.ts +170 -0
  318. package/src/flow-compat/FieldValidation.tsx +122 -60
  319. package/src/flow-compat/Popover.tsx +43 -4
  320. package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
  321. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  322. package/src/flow-compat/routeTypes.ts +1 -0
  323. package/src/index.ts +14 -1
  324. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
  325. package/src/locale/languageCodes.ts +3 -1
  326. package/src/nocobase-buildin-plugin/index.tsx +63 -15
  327. package/src/settings-center/AdminSettingsLayout.tsx +10 -2
  328. package/src/settings-center/SystemSettingsPage.tsx +1 -2
  329. package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
  330. package/src/settings-center/plugin-manager/index.tsx +3 -0
  331. package/src/settings-center/utils.tsx +0 -6
  332. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  333. package/src/utils/index.tsx +1 -0
  334. package/src/utils/markdownSanitize.ts +88 -0
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { describe, expect, it, vi } from 'vitest';
11
11
  import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
12
- import { fieldLinkageRules } from '../linkageRules';
12
+ import { fieldLinkageRules, linkageAssignField } from '../linkageRules';
13
13
 
14
14
  describe('fieldLinkageRules action - linkage scope metadata', () => {
15
15
  const targetCollection: any = {
@@ -868,6 +868,155 @@ describe('fieldLinkageRules action - linkage scope metadata', () => {
868
868
  expect(setFormValues).toHaveBeenCalledTimes(2);
869
869
  });
870
870
 
871
+ it('keeps row-scoped override patches editable after the target is user edited', async () => {
872
+ const store = {
873
+ roles: [{ uid: 'role-uid-1', name: 'existing' }],
874
+ };
875
+ const getAt = (path: Array<string | number>) => path.reduce((acc: any, seg) => acc?.[seg], store as any);
876
+ const setAt = (path: Array<string | number>, value: any) => {
877
+ let cur: any = store;
878
+ for (const seg of path.slice(0, -1)) {
879
+ cur = cur[seg];
880
+ }
881
+ cur[path[path.length - 1]] = value;
882
+ };
883
+ const editedKeys = new Set<string>();
884
+ const toKey = (path: Array<string | number>) => JSON.stringify(path);
885
+ const form = {
886
+ getFieldValue: (path: Array<string | number>) => getAt(path),
887
+ };
888
+ const formValueRuntime = {
889
+ canApplyOverrideValuePatch: vi.fn((path: Array<string | number>) => !editedKeys.has(toKey(path))),
890
+ };
891
+ const setFormValues = vi.fn(async (patches: Array<{ path: Array<string | number>; value: any }>) => {
892
+ for (const patch of patches) {
893
+ setAt(patch.path, patch.value);
894
+ }
895
+ });
896
+
897
+ const engine = new FlowEngine();
898
+ const rowFork = new FlowModel({ uid: 'role-override-row-fork', flowEngine: engine }) as any;
899
+ rowFork.context.defineProperty('fieldIndex', {
900
+ value: ['roles:0'],
901
+ });
902
+ rowFork.context.defineProperty('form', {
903
+ value: form,
904
+ });
905
+ rowFork.context.defineProperty('setFormValues', {
906
+ value: setFormValues,
907
+ });
908
+ rowFork.context.defineProperty('app', {
909
+ value: {
910
+ jsonLogic: {
911
+ apply: () => true,
912
+ },
913
+ },
914
+ });
915
+ rowFork.subModels = { items: [] };
916
+ rowFork.getAction = vi.fn((name: string) => {
917
+ if (name === 'linkageAssignField') {
918
+ return linkageAssignField;
919
+ }
920
+ });
921
+
922
+ const masterModel: any = {
923
+ uid: 'master-role-override-grid',
924
+ forks: new Set([rowFork]),
925
+ };
926
+ const rolesField: any = {
927
+ type: 'hasMany',
928
+ isAssociationField: () => true,
929
+ targetCollection: {
930
+ getField: (name: string) => ({ name, isAssociationField: () => false }),
931
+ },
932
+ };
933
+ const blockModel: any = {
934
+ collection: {
935
+ getField: (name: string) => (name === 'roles' ? rolesField : null),
936
+ },
937
+ formValueRuntime,
938
+ };
939
+ rowFork.context.defineProperty('blockModel', {
940
+ value: blockModel,
941
+ });
942
+ const gridModel: any = {
943
+ uid: 'grid-model-role-override-row',
944
+ context: {
945
+ blockModel,
946
+ },
947
+ getAction: vi.fn((name: string) => {
948
+ if (name === 'linkageAssignField') {
949
+ return linkageAssignField;
950
+ }
951
+ }),
952
+ __allModels: [],
953
+ };
954
+ const ctx: any = {
955
+ model: gridModel,
956
+ engine: {
957
+ forEachModel: (cb: (m: any) => void) => {
958
+ cb(masterModel);
959
+ },
960
+ },
961
+ flowKey: 'eventSettings',
962
+ inputArgs: {
963
+ source: 'user',
964
+ txId: 'tx-role-override-row',
965
+ changedPaths: [['roles', 0, 'uid']],
966
+ },
967
+ app: {
968
+ jsonLogic: {
969
+ apply: () => true,
970
+ },
971
+ },
972
+ resolveJsonTemplate: async (v: any) => v,
973
+ };
974
+ const makeParams = (value: string) => ({
975
+ value: [
976
+ {
977
+ key: `rule-${value}`,
978
+ title: `rule-${value}`,
979
+ enable: true,
980
+ condition: { logic: '$and', items: [] },
981
+ actions: [
982
+ {
983
+ name: 'linkageAssignField',
984
+ params: {
985
+ value: [
986
+ {
987
+ key: `r-${value}`,
988
+ enable: true,
989
+ targetPath: 'roles.name',
990
+ mode: 'override',
991
+ value,
992
+ condition: { logic: '$and', items: [] },
993
+ },
994
+ ],
995
+ },
996
+ },
997
+ ],
998
+ },
999
+ ],
1000
+ });
1001
+
1002
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-1'));
1003
+ expect(store.roles[0].name).toBe('role-uid-1');
1004
+
1005
+ store.roles[0].uid = 'role-uid-2';
1006
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-2'));
1007
+ expect(store.roles[0].name).toBe('role-uid-2');
1008
+ expect(setFormValues).toHaveBeenCalledTimes(2);
1009
+
1010
+ store.roles[0].name = 'manual';
1011
+ store.roles[0].uid = 'role-uid-3';
1012
+ editedKeys.add(toKey(['roles', 0, 'name']));
1013
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-3'));
1014
+
1015
+ expect(store.roles[0].name).toBe('manual');
1016
+ expect(setFormValues).toHaveBeenCalledTimes(2);
1017
+ expect(formValueRuntime.canApplyOverrideValuePatch).toHaveBeenCalledWith(['roles', 0, 'name']);
1018
+ });
1019
+
871
1020
  it('skips stale subtable row forks after the row has been removed', async () => {
872
1021
  const setFormValues = vi.fn(async () => undefined);
873
1022
  const defaultHandler = vi.fn((actionCtx: any, { addFormValuePatch }: any) => {
@@ -69,6 +69,72 @@ describe('linkage assign actions - legacy params', () => {
69
69
  expect(setProps).not.toHaveBeenCalledWith(fieldModel, { value: 'default-in-update' });
70
70
  });
71
71
 
72
+ it('linkageAssignField should mark override mode for field targets', async () => {
73
+ const fieldModel: any = { uid: 'f-override', fieldPath: 'a.b' };
74
+ const ctx: any = {
75
+ model: {
76
+ subModels: { grid: { subModels: { items: [fieldModel] } } },
77
+ },
78
+ engine: { getModel: vi.fn(() => ({ fieldPath: 'a.b' })) },
79
+ app: { jsonLogic: { apply: vi.fn() } },
80
+ };
81
+ const setProps = vi.fn();
82
+
83
+ await linkageAssignField.handler(ctx, {
84
+ value: [
85
+ {
86
+ key: 'r-override',
87
+ enable: true,
88
+ targetPath: 'a.b',
89
+ mode: 'override',
90
+ condition: { logic: '$and', items: [] },
91
+ value: 'override-value',
92
+ },
93
+ ],
94
+ setProps,
95
+ });
96
+
97
+ expect(setProps).toHaveBeenCalledTimes(1);
98
+ const [, props] = setProps.mock.calls[0];
99
+ expect(props).toMatchObject({
100
+ value: 'override-value',
101
+ __linkageAssignMode: 'override',
102
+ });
103
+ });
104
+
105
+ it('linkageAssignField should emit override patches for targets without form item models', async () => {
106
+ const ctx: any = {
107
+ model: { subModels: { grid: { subModels: { items: [] } } } },
108
+ engine: { getModel: vi.fn(() => null) },
109
+ app: { jsonLogic: { apply: vi.fn() } },
110
+ };
111
+ const setProps = vi.fn();
112
+ const addFormValuePatch = vi.fn();
113
+
114
+ await linkageAssignField.handler(ctx, {
115
+ value: [
116
+ {
117
+ key: 'r-override-direct',
118
+ enable: true,
119
+ targetPath: 'user.name',
120
+ mode: 'override',
121
+ condition: { logic: '$and', items: [] },
122
+ value: 'override-direct',
123
+ },
124
+ ],
125
+ setProps,
126
+ addFormValuePatch,
127
+ });
128
+
129
+ expect(setProps).not.toHaveBeenCalled();
130
+ expect(addFormValuePatch).toHaveBeenCalledWith({
131
+ path: 'user.name',
132
+ value: 'override-direct',
133
+ whenEmpty: false,
134
+ mode: 'override',
135
+ });
136
+ });
137
+
72
138
  it('subFormLinkageAssignField should apply legacy object params at runtime', () => {
73
139
  const forkModel: any = {
74
140
  uid: 'f1',
@@ -381,6 +447,75 @@ describe('linkage assign actions - legacy params', () => {
381
447
  expect(addFormValuePatch).toHaveBeenCalledTimes(1);
382
448
  });
383
449
 
450
+ it('subFormLinkageAssignField should skip override patch after user edits the target path', () => {
451
+ const ctx: any = {
452
+ model: {
453
+ uid: 'sub-form-override-path',
454
+ getAclActionName: vi.fn(() => 'create'),
455
+ context: {},
456
+ parent: {
457
+ getStepParams: (flowKey: string, stepKey: string) => {
458
+ if (flowKey === 'fieldSettings' && stepKey === 'init') {
459
+ return { fieldPath: 'M2M.M2M' };
460
+ }
461
+ return {};
462
+ },
463
+ },
464
+ subModels: { grid: { subModels: { items: [] } } },
465
+ },
466
+ engine: {
467
+ getModel: vi.fn(() => null),
468
+ },
469
+ app: { jsonLogic: { apply: vi.fn() } },
470
+ };
471
+ const addFormValuePatch = vi.fn();
472
+
473
+ subFormLinkageAssignField.handler(ctx, {
474
+ value: [
475
+ {
476
+ key: 'r-override-path-1',
477
+ enable: true,
478
+ targetPath: 'Name',
479
+ mode: 'override',
480
+ condition: { logic: '$and', items: [] },
481
+ value: 'override-1',
482
+ },
483
+ ],
484
+ addFormValuePatch,
485
+ setProps: vi.fn(),
486
+ });
487
+
488
+ expect(addFormValuePatch).toHaveBeenCalledTimes(1);
489
+ expect(addFormValuePatch).toHaveBeenNthCalledWith(1, {
490
+ path: 'M2M.M2M.Name',
491
+ value: 'override-1',
492
+ whenEmpty: false,
493
+ mode: 'override',
494
+ });
495
+
496
+ ctx.inputArgs = {
497
+ source: 'user',
498
+ changedPaths: [['M2M', 'M2M', 'Name']],
499
+ };
500
+
501
+ subFormLinkageAssignField.handler(ctx, {
502
+ value: [
503
+ {
504
+ key: 'r-override-path-2',
505
+ enable: true,
506
+ targetPath: 'Name',
507
+ mode: 'override',
508
+ condition: { logic: '$and', items: [] },
509
+ value: 'override-2',
510
+ },
511
+ ],
512
+ addFormValuePatch,
513
+ setProps: vi.fn(),
514
+ });
515
+
516
+ expect(addFormValuePatch).toHaveBeenCalledTimes(1);
517
+ });
518
+
384
519
  it('linkageAssignField should evaluate RunJS value before assign', async () => {
385
520
  const fieldModel: any = { uid: 'f-runjs-assign', fieldPath: 'a.b' };
386
521
  const runjs = vi.fn(async () => ({ success: true, value: 'from-runjs' }));
@@ -27,12 +27,12 @@ describe('linkageRules hidden state propagation', () => {
27
27
  const hostModel: any = {
28
28
  uid: 'host',
29
29
  hidden: false,
30
- context: { blockModel },
30
+ context: { blockModel, fieldPathArray: ['children'] },
31
31
  __allModels: [],
32
32
  setProps: vi.fn(),
33
33
  getFlow: vi.fn(() => ({})),
34
34
  getStepParams: vi.fn((_flowKey: string, stepKey: string) => {
35
- if (stepKey === 'init') return { fieldPath: 'org_m2o' };
35
+ if (stepKey === 'init') return { fieldPath: 'children' };
36
36
  return undefined;
37
37
  }),
38
38
  translate: (s: string) => s,
@@ -41,10 +41,22 @@ describe('linkageRules hidden state propagation', () => {
41
41
  const sameTargetModel: any = {
42
42
  uid: 'same-target',
43
43
  hidden: false,
44
- context: { blockModel },
44
+ context: { blockModel, fieldPathArray: ['children'] },
45
45
  setProps: vi.fn(),
46
46
  getStepParams: vi.fn((_flowKey: string, stepKey: string) => {
47
- if (stepKey === 'init') return { fieldPath: 'org_m2o' };
47
+ if (stepKey === 'init') return { fieldPath: 'children' };
48
+ return undefined;
49
+ }),
50
+ translate: (s: string) => s,
51
+ };
52
+
53
+ const nestedColumnModel: any = {
54
+ uid: 'nested-column',
55
+ hidden: true,
56
+ context: { blockModel, fieldPathArray: ['children'] },
57
+ setProps: vi.fn(),
58
+ getStepParams: vi.fn((_flowKey: string, stepKey: string) => {
59
+ if (stepKey === 'init') return { fieldPath: 'children.children' };
48
60
  return undefined;
49
61
  }),
50
62
  translate: (s: string) => s,
@@ -52,7 +64,7 @@ describe('linkageRules hidden state propagation', () => {
52
64
 
53
65
  const engine = {
54
66
  forEachModel: (visitor: (m: any) => void) => {
55
- [hostModel, sameTargetModel].forEach(visitor);
67
+ [hostModel, sameTargetModel, nestedColumnModel].forEach(visitor);
56
68
  },
57
69
  };
58
70
 
@@ -92,9 +104,11 @@ describe('linkageRules hidden state propagation', () => {
92
104
  });
93
105
  expect(hostModel.hidden).toBe(true);
94
106
  expect(sameTargetModel.hidden).toBe(true);
107
+ expect(nestedColumnModel.hidden).toBe(true);
95
108
 
96
109
  await actionLinkageRules.handler(ctx, { value: [] });
97
110
  expect(hostModel.hidden).toBe(false);
98
111
  expect(sameTargetModel.hidden).toBe(false);
112
+ expect(nestedColumnModel.hidden).toBe(true);
99
113
  });
100
114
  });
@@ -728,6 +728,98 @@ describe('linkageSetFieldProps action', () => {
728
728
  expect(form.setFieldValue).not.toHaveBeenCalled();
729
729
  });
730
730
 
731
+ it('should not clear the parent field when a nested same-name field inherits the parent path', async () => {
732
+ const setFormValues = vi.fn(async () => undefined);
733
+ const form = {
734
+ getFieldValue: vi.fn((path: Array<string | number>) => {
735
+ if (JSON.stringify(path) === JSON.stringify(['children', 'children'])) {
736
+ return [{ title: 'nested' }];
737
+ }
738
+ if (JSON.stringify(path) === JSON.stringify(['children'])) {
739
+ return [{ title: 'parent' }];
740
+ }
741
+ return undefined;
742
+ }),
743
+ setFieldValue: vi.fn(),
744
+ };
745
+ const fieldModel: any = {
746
+ uid: 'nested-children-field',
747
+ hidden: false,
748
+ context: {
749
+ form,
750
+ fieldPathArray: ['children'],
751
+ },
752
+ props: {
753
+ label: 'Children',
754
+ },
755
+ getStepParams: vi.fn((flowKey: string, stepKey: string) => {
756
+ if (flowKey === 'fieldSettings' && stepKey === 'init') {
757
+ return { fieldPath: 'children.children' };
758
+ }
759
+ }),
760
+ setProps(key: any, value?: any) {
761
+ if (typeof key === 'string') {
762
+ this.props[key] = value;
763
+ } else {
764
+ this.props = { ...this.props, ...key };
765
+ }
766
+ },
767
+ };
768
+ const ctx: any = {
769
+ app: {
770
+ jsonLogic: {
771
+ apply: vi.fn(() => true),
772
+ },
773
+ },
774
+ model: {
775
+ context: { form },
776
+ subModels: {
777
+ grid: {
778
+ subModels: {
779
+ items: [fieldModel],
780
+ },
781
+ },
782
+ },
783
+ },
784
+ setFormValues,
785
+ getAction: (name: string) => (name === 'linkageSetFieldProps' ? linkageSetFieldProps : null),
786
+ resolveJsonTemplate: vi.fn(async (value) => value),
787
+ };
788
+
789
+ await fieldLinkageRules.handler(ctx, {
790
+ value: [
791
+ {
792
+ key: 'rule-1',
793
+ enable: true,
794
+ condition: { logic: '$and', items: [] },
795
+ actions: [
796
+ {
797
+ key: 'action-1',
798
+ name: 'linkageSetFieldProps',
799
+ params: {
800
+ value: {
801
+ fields: ['nested-children-field'],
802
+ state: 'hidden',
803
+ },
804
+ },
805
+ },
806
+ ],
807
+ },
808
+ ],
809
+ });
810
+
811
+ expect(fieldModel.hidden).toBe(true);
812
+ expect(setFormValues).toHaveBeenCalledWith(
813
+ [{ path: ['children', 'children'], value: undefined }],
814
+ expect.objectContaining({ source: 'linkage' }),
815
+ );
816
+ expect(setFormValues).not.toHaveBeenCalledWith(
817
+ [{ path: ['children'], value: undefined }],
818
+ expect.objectContaining({ source: 'linkage' }),
819
+ );
820
+ expect(form.setFieldValue).not.toHaveBeenCalled();
821
+ });
822
+
731
823
  it('should keep hidden clear after same-round assignment for the same field', async () => {
732
824
  const formValues: { name?: string } = {};
733
825
  const setFormValues = vi.fn(async (patches: Array<{ path: Array<string | number>; value: string | undefined }>) => {
@@ -0,0 +1,171 @@
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 { ActionScene } from '@nocobase/flow-engine';
11
+ import { describe, expect, it, vi } from 'vitest';
12
+
13
+ import * as registeredActions from '../index';
14
+ import * as linkageActions from '../linkageRules';
15
+
16
+ const tabLinkageRules = (linkageActions as Record<string, unknown>).tabLinkageRules as
17
+ | typeof linkageActions.menuLinkageRules
18
+ | undefined;
19
+ const linkageSetTabProps = (linkageActions as Record<string, unknown>).linkageSetTabProps as
20
+ | typeof linkageActions.linkageSetMenuItemProps
21
+ | undefined;
22
+
23
+ function createRuntime() {
24
+ let matched = true;
25
+ const setProps = vi.fn(function (this: { props: Record<string, unknown> }, props: Record<string, unknown>) {
26
+ this.props = {
27
+ ...this.props,
28
+ ...props,
29
+ };
30
+ });
31
+ const setHidden = vi.fn(function (this: { hidden: boolean }, value: boolean) {
32
+ this.hidden = value;
33
+ });
34
+ const model = {
35
+ uid: 'tab-1',
36
+ props: {} as Record<string, unknown>,
37
+ hidden: false,
38
+ __allModels: [],
39
+ setProps,
40
+ setHidden,
41
+ };
42
+ const ctx = {
43
+ app: { jsonLogic: { apply: () => matched } },
44
+ model,
45
+ t: (text: string) => text,
46
+ getAction: (name: string) => (name === 'linkageSetTabProps' ? linkageSetTabProps : undefined),
47
+ resolveJsonTemplate: vi.fn(async (value: unknown) => value),
48
+ };
49
+
50
+ return {
51
+ ctx,
52
+ model,
53
+ setMatched(value: boolean) {
54
+ matched = value;
55
+ },
56
+ };
57
+ }
58
+
59
+ const hideRule = {
60
+ value: [
61
+ {
62
+ key: 'rule-1',
63
+ title: 'Hide tab',
64
+ enable: true,
65
+ condition: {
66
+ logic: '$and',
67
+ items: [{ path: 'status', operator: '$eq', value: 'visible' }],
68
+ },
69
+ actions: [
70
+ {
71
+ key: 'action-1',
72
+ name: 'linkageSetTabProps',
73
+ params: { value: 'hidden' },
74
+ },
75
+ ],
76
+ },
77
+ ],
78
+ };
79
+
80
+ describe('tab linkage rules', () => {
81
+ it('should append the tab linkage scene without changing existing scene values', () => {
82
+ const scenes = ActionScene as unknown as Record<string, number>;
83
+
84
+ expect(scenes.MENU_LINKAGE_RULES).toBe(7);
85
+ expect(scenes.TAB_LINKAGE_RULES).toBe(8);
86
+ expect(scenes.TAB_LINKAGE_RULES).toBe(Math.max(...Object.values(scenes).filter(Number.isInteger)));
87
+ });
88
+
89
+ it('should register tab linkage actions from the actions entry', () => {
90
+ expect((registeredActions as Record<string, unknown>).tabLinkageRules).toBe(tabLinkageRules);
91
+ expect((registeredActions as Record<string, unknown>).linkageSetTabProps).toBe(linkageSetTabProps);
92
+ expect(tabLinkageRules).toBeDefined();
93
+ expect(linkageSetTabProps).toBeDefined();
94
+ });
95
+
96
+ it('should expose tab state and runjs actions in ui schema', () => {
97
+ expect(tabLinkageRules).toBeDefined();
98
+ expect(linkageSetTabProps).toBeDefined();
99
+
100
+ const schema = tabLinkageRules?.uiSchema?.({
101
+ getActions: () =>
102
+ new Map([
103
+ ['linkageSetTabProps', linkageSetTabProps],
104
+ ['linkageRunjs', linkageActions.linkageRunjs],
105
+ ['linkageSetMenuItemProps', linkageActions.linkageSetMenuItemProps],
106
+ ]),
107
+ } as never) as Record<string, Record<string, Record<string, unknown>>> | undefined;
108
+
109
+ expect(schema?.value?.['x-component-props']?.supportedActions).toEqual(['linkageSetTabProps', 'linkageRunjs']);
110
+ expect(linkageSetTabProps?.scene).toBe((ActionScene as unknown as Record<string, number>).TAB_LINKAGE_RULES);
111
+ expect(linkageActions.linkageRunjs.scene).toContain(
112
+ (ActionScene as unknown as Record<string, number>).TAB_LINKAGE_RULES,
113
+ );
114
+ });
115
+
116
+ it('should hide the tab when the condition matches and restore it when the condition misses', async () => {
117
+ expect(tabLinkageRules).toBeDefined();
118
+ const runtime = createRuntime();
119
+
120
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
121
+ expect(runtime.model.hidden).toBe(true);
122
+
123
+ runtime.setMatched(false);
124
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
125
+ expect(runtime.model.hidden).toBe(false);
126
+ });
127
+
128
+ it('should restore the tab when linkage rules are cleared', async () => {
129
+ expect(tabLinkageRules).toBeDefined();
130
+ const runtime = createRuntime();
131
+
132
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
133
+ expect(runtime.model.hidden).toBe(true);
134
+
135
+ await tabLinkageRules?.handler(runtime.ctx as never, { value: [] });
136
+ expect(runtime.model.hidden).toBe(false);
137
+ });
138
+
139
+ it('should not restore stale tab props while applying or clearing visibility rules', async () => {
140
+ expect(tabLinkageRules).toBeDefined();
141
+ const runtime = createRuntime();
142
+ runtime.model.props = {
143
+ title: 'Original title',
144
+ icon: 'original-icon',
145
+ route: { options: { badge: 'original' } },
146
+ };
147
+
148
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
149
+ expect(runtime.model.hidden).toBe(true);
150
+
151
+ const updatedProps = {
152
+ title: 'Updated title',
153
+ icon: 'updated-icon',
154
+ route: { options: { badge: 'updated' } },
155
+ };
156
+ runtime.model.props = updatedProps;
157
+
158
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
159
+ expect(runtime.model.props).toMatchObject(updatedProps);
160
+ expect(runtime.model.hidden).toBe(true);
161
+
162
+ runtime.setMatched(false);
163
+ await tabLinkageRules?.handler(runtime.ctx as never, hideRule);
164
+ expect(runtime.model.props).toMatchObject(updatedProps);
165
+ expect(runtime.model.hidden).toBe(false);
166
+
167
+ await tabLinkageRules?.handler(runtime.ctx as never, { value: [] });
168
+ expect(runtime.model.props).toMatchObject(updatedProps);
169
+ expect(runtime.model.hidden).toBe(false);
170
+ });
171
+ });
@@ -135,11 +135,15 @@ describe('linkageRulesRefresh action', () => {
135
135
  expect(handler).toHaveBeenCalledWith(ctx, { value: ['master-mounted'] });
136
136
  });
137
137
 
138
- it('runs linkage action on master model when forks exist in design mode', async () => {
138
+ it('skips master model when forks can handle flow in design mode', async () => {
139
139
  const handler = vi.fn(async () => {});
140
140
  const model: any = {
141
141
  isFork: false,
142
- forks: new Set([{}]),
142
+ forks: new Set([
143
+ {
144
+ getFlow: vi.fn(() => ({})),
145
+ },
146
+ ]),
143
147
  getFlow: vi.fn(() => ({})),
144
148
  getStepParams: vi.fn(() => ({ value: ['master'] })),
145
149
  context: {
@@ -157,7 +161,7 @@ describe('linkageRulesRefresh action', () => {
157
161
  flowKey: 'buttonSettings',
158
162
  });
159
163
 
160
- expect(handler).toHaveBeenCalledWith(ctx, { value: ['master'] });
164
+ expect(handler).not.toHaveBeenCalled();
161
165
  });
162
166
 
163
167
  it('runs linkage action on fork model and resolves params', async () => {