@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
@@ -42,6 +42,7 @@ import {
42
42
  FLOW_SETTINGS_PREFERENCE_STORAGE_KEY,
43
43
  readFlowSettingsPreference,
44
44
  } from './flowSettingsPreference';
45
+ import { joinAdminLayoutRoutePath, type AdminLayoutRoutePathLike } from './resolveAdminRouteRuntimeTarget';
45
46
  import { useAppListRender } from './AppListRender';
46
47
 
47
48
  const className1 = css`
@@ -284,13 +285,44 @@ function SetIsMobileLayout(props: { isMobile: boolean; children: any; model?: Ad
284
285
  const flowEngine = useFlowEngine();
285
286
  const adminLayoutModel = props.model || flowEngine.getModel<AdminLayoutModel>(ADMIN_LAYOUT_MODEL_UID);
286
287
 
287
- useEffect(() => {
288
+ useLayoutEffect(() => {
288
289
  adminLayoutModel?.setIsMobileLayout(props.isMobile);
289
290
  }, [adminLayoutModel, props.isMobile]);
290
291
 
291
292
  return props.children;
292
293
  }
293
294
 
295
+ function AdminLayoutContentWithMobileState(props: {
296
+ isMobile: boolean;
297
+ model?: AdminLayoutModel;
298
+ designable?: boolean;
299
+ layout?: AdminLayoutModel['layout'];
300
+ onContentElementChange?: (element: HTMLDivElement | null) => void;
301
+ }) {
302
+ const modelRef = useRef(props.model);
303
+ const isMobileRef = useRef(props.isMobile);
304
+ const onContentElementChangeRef = useRef(props.onContentElementChange);
305
+
306
+ modelRef.current = props.model;
307
+ isMobileRef.current = props.isMobile;
308
+ onContentElementChangeRef.current = props.onContentElementChange;
309
+
310
+ const handleContentElementChange = useCallback((element: HTMLDivElement | null) => {
311
+ if (element) {
312
+ modelRef.current?.setIsMobileLayout(isMobileRef.current);
313
+ }
314
+ onContentElementChangeRef.current?.(element);
315
+ }, []);
316
+
317
+ return (
318
+ <AdminLayoutContent
319
+ designable={props.designable}
320
+ layout={props.layout}
321
+ onContentElementChange={handleContentElementChange}
322
+ />
323
+ );
324
+ }
325
+
294
326
  const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRoute?: NocoBaseDesktopRoute }> = (
295
327
  props,
296
328
  ) => {
@@ -314,25 +346,37 @@ const DesignerButtonMenuItem: FC<{ item: AdminLayoutMenuNode; fallbackParentRout
314
346
  );
315
347
  };
316
348
 
317
- const matchesRoutePath = (route: NocoBaseDesktopRoute | undefined, pathname: string): boolean => {
349
+ const matchesRoutePath = (
350
+ route: NocoBaseDesktopRoute | undefined,
351
+ pathname: string,
352
+ layout?: AdminLayoutRoutePathLike | null,
353
+ ): boolean => {
318
354
  if (!route) {
319
355
  return false;
320
356
  }
321
357
 
322
358
  const candidates = [
323
- route.id != null ? `/admin/${route.id}` : null,
324
- route.schemaUid ? `/admin/${route.schemaUid}` : null,
359
+ route.id != null ? joinAdminLayoutRoutePath(layout, route.id) : null,
360
+ route.schemaUid ? joinAdminLayoutRoutePath(layout, route.schemaUid) : null,
325
361
  ].filter(Boolean) as string[];
326
362
 
327
363
  if (candidates.some((candidate) => pathname === candidate || pathname.startsWith(`${candidate}/`))) {
328
364
  return true;
329
365
  }
330
366
 
331
- return Array.isArray(route.children) ? route.children.some((child) => matchesRoutePath(child, pathname)) : false;
367
+ return Array.isArray(route.children)
368
+ ? route.children.some((child) => matchesRoutePath(child, pathname, layout))
369
+ : false;
332
370
  };
333
371
 
334
- const findSelectedTopGroupRoute = (routes: NocoBaseDesktopRoute[], pathname: string) => {
335
- return routes.find((route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname));
372
+ const findSelectedTopGroupRoute = (
373
+ routes: NocoBaseDesktopRoute[],
374
+ pathname: string,
375
+ layout?: AdminLayoutRoutePathLike | null,
376
+ ) => {
377
+ return routes.find(
378
+ (route) => route.type === NocoBaseDesktopRouteType.group && matchesRoutePath(route, pathname, layout),
379
+ );
336
380
  };
337
381
 
338
382
  const renderMenuNodeWithModel = (
@@ -376,6 +420,11 @@ export const AdminLayoutComponent = observer((props: any) => {
376
420
  const { token } = antdTheme.useToken();
377
421
  const customToken = token as CustomToken;
378
422
  const isMobileLayout = !!adminLayoutModel?.isMobileLayout;
423
+ const adminLayoutRoutePath = adminLayoutModel?.layout?.routePath;
424
+ const adminLayoutRoutePathLike = useMemo<AdminLayoutRoutePathLike | undefined>(
425
+ () => (adminLayoutRoutePath ? { routePath: adminLayoutRoutePath } : undefined),
426
+ [adminLayoutRoutePath],
427
+ );
379
428
  const menuRouteRefreshVersion = adminLayoutModel?.menuRouteRefreshVersion || 0;
380
429
  const isMobileSider = isMobileLayout;
381
430
  const [collapsed, setCollapsed] = useState(isMobileSider);
@@ -392,8 +441,8 @@ export const AdminLayoutComponent = observer((props: any) => {
392
441
  );
393
442
  const designable = !isMobileSider && preferredFlowSettingsEnabled;
394
443
  const { styles } = useHeaderStyle();
395
- const { appList } = useApplications();
396
- const appListRender = useAppListRender();
444
+ const { appList, appSwitcherModel } = useApplications(adminLayoutModel);
445
+ const appListRender = useAppListRender(appSwitcherModel);
397
446
  const flowSettingsSyncRef = useRef(0);
398
447
  const desiredFlowSettingsEnabledRef = useRef(false);
399
448
  const handleLayoutContentElementChange = useCallback(
@@ -403,8 +452,8 @@ export const AdminLayoutComponent = observer((props: any) => {
403
452
  [adminLayoutModel],
404
453
  );
405
454
  const selectedTopGroupRoute = useMemo(
406
- () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname),
407
- [allAccessRoutes, location.pathname],
455
+ () => findSelectedTopGroupRoute(allAccessRoutes, location.pathname, adminLayoutRoutePathLike),
456
+ [adminLayoutRoutePathLike, allAccessRoutes, location.pathname],
408
457
  );
409
458
 
410
459
  const handleMenuDragEnd = useCallback(
@@ -458,6 +507,7 @@ export const AdminLayoutComponent = observer((props: any) => {
458
507
 
459
508
  useEffect(() => {
460
509
  const routeRepository = flowEngine.context.routeRepository;
510
+ const deactivateLayout = routeRepository?.activateLayout?.(adminLayoutModel?.layout);
461
511
  const subscriber = () => {
462
512
  const updatedRoutes = routeRepository?.listAccessible() || [];
463
513
  setAllAccessRoutes(updatedRoutes);
@@ -470,8 +520,9 @@ export const AdminLayoutComponent = observer((props: any) => {
470
520
 
471
521
  return () => {
472
522
  routeRepository?.unsubscribe(subscriber);
523
+ deactivateLayout?.();
473
524
  };
474
- }, [flowEngine]);
525
+ }, [adminLayoutModel, adminLayoutModel?.layout?.uid, flowEngine]);
475
526
 
476
527
  useEffect(() => {
477
528
  if (typeof window === 'undefined') {
@@ -614,6 +665,10 @@ export const AdminLayoutComponent = observer((props: any) => {
614
665
  z-index: 2000 !important;
615
666
  }
616
667
 
668
+ .ant-pro-layout-apps-popover .ant-popover-content {
669
+ margin-top: 12px;
670
+ }
671
+
617
672
  .ant-pro-layout-apps-icon {
618
673
  color: ${customToken.colorTextHeaderMenu || 'rgba(255, 255, 255, 0.85)'};
619
674
  }
@@ -657,7 +712,6 @@ export const AdminLayoutComponent = observer((props: any) => {
657
712
  <MobileMenuControlContext.Provider value={{ closeMobileMenu }}>
658
713
  <DndProvider collisionDetection={menuCollisionDetection} onDragEnd={handleMenuDragEnd}>
659
714
  <ProLayout
660
- key={`admin-layout-menu-${menuRouteRefreshVersion}`}
661
715
  {...props}
662
716
  contentStyle={contentStyle}
663
717
  siderWidth={customToken.siderWidth || 200}
@@ -697,7 +751,13 @@ export const AdminLayoutComponent = observer((props: any) => {
697
751
  <SetIsMobileLayout isMobile={isMobile} model={adminLayoutModel}>
698
752
  <ConfigProvider theme={isMobile ? mobileTheme : theme}>
699
753
  <GlobalStyle />
700
- <AdminLayoutContent onContentElementChange={handleLayoutContentElementChange} />
754
+ <AdminLayoutContentWithMobileState
755
+ isMobile={isMobile}
756
+ model={adminLayoutModel}
757
+ designable={designable}
758
+ layout={adminLayoutModel?.layout}
759
+ onContentElementChange={handleLayoutContentElementChange}
760
+ />
701
761
  </ConfigProvider>
702
762
  </SetIsMobileLayout>
703
763
  );
@@ -0,0 +1,486 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
11
+ import { act, render, screen, waitFor } from '@testing-library/react';
12
+ import React from 'react';
13
+ import { createMemoryRouter, Outlet, RouterProvider } from 'react-router-dom';
14
+ import { describe, expect, it, vi } from 'vitest';
15
+ import { NocoBaseDesktopRouteType } from '../../../flow-compat';
16
+ import { RouteRepository } from '../../../RouteRepository';
17
+ import { AdminLayoutEntryGuard } from './AdminLayoutEntryGuard';
18
+ import type { AdminLayoutModel } from './AdminLayoutModel';
19
+
20
+ describe('AdminLayoutEntryGuard', () => {
21
+ it('should ensure accessible routes for the active layout before resolving the landing route', async () => {
22
+ const engine = new FlowEngine();
23
+ const request = vi.fn().mockResolvedValue({
24
+ data: {
25
+ data: [],
26
+ },
27
+ });
28
+ const routeRepository = new RouteRepository({
29
+ api: {
30
+ request,
31
+ resource: vi.fn(),
32
+ },
33
+ } as never);
34
+ routeRepository.setRoutes([], 'admin-layout-model');
35
+ const deactivateLayout = routeRepository.activateLayout({
36
+ uid: 'mobile-layout-model',
37
+ });
38
+ engine.context.defineProperty('routeRepository', {
39
+ value: routeRepository,
40
+ });
41
+ engine.context.defineProperty('app', {
42
+ value: {
43
+ router: {
44
+ getBasename: () => '',
45
+ },
46
+ },
47
+ });
48
+
49
+ const model = {
50
+ layout: {
51
+ routeName: 'mobile',
52
+ routePath: '/mobile',
53
+ uid: 'mobile-layout-model',
54
+ },
55
+ } as AdminLayoutModel;
56
+ const router = createMemoryRouter(
57
+ [
58
+ {
59
+ path: '/mobile',
60
+ id: 'mobile',
61
+ element: (
62
+ <FlowEngineProvider engine={engine}>
63
+ <AdminLayoutEntryGuard model={model}>
64
+ <div>Mobile layout shell</div>
65
+ </AdminLayoutEntryGuard>
66
+ </FlowEngineProvider>
67
+ ),
68
+ },
69
+ ],
70
+ {
71
+ initialEntries: ['/mobile'],
72
+ },
73
+ );
74
+
75
+ render(<RouterProvider router={router} />);
76
+
77
+ await waitFor(() => {
78
+ expect(request).toHaveBeenCalledWith({
79
+ url: '/desktopRoutes:listAccessible',
80
+ params: {
81
+ tree: true,
82
+ sort: 'sort',
83
+ layout: 'mobile-layout-model',
84
+ },
85
+ });
86
+ });
87
+ expect(screen.getByText('Mobile layout shell')).toBeInTheDocument();
88
+ deactivateLayout();
89
+ });
90
+
91
+ it('should activate the guard layout before loading accessible routes', async () => {
92
+ const engine = new FlowEngine();
93
+ const request = vi.fn().mockResolvedValue({
94
+ data: {
95
+ data: [],
96
+ },
97
+ });
98
+ const routeRepository = new RouteRepository({
99
+ api: {
100
+ request,
101
+ resource: vi.fn(),
102
+ },
103
+ } as never);
104
+ engine.context.defineProperty('routeRepository', {
105
+ value: routeRepository,
106
+ });
107
+ engine.context.defineProperty('app', {
108
+ value: {
109
+ router: {
110
+ getBasename: () => '',
111
+ },
112
+ },
113
+ });
114
+
115
+ const model = {
116
+ layout: {
117
+ routeName: 'mobile',
118
+ routePath: '/mobile',
119
+ uid: 'mobile-layout-model',
120
+ },
121
+ } as AdminLayoutModel;
122
+ const router = createMemoryRouter(
123
+ [
124
+ {
125
+ path: '/mobile',
126
+ id: 'mobile',
127
+ element: (
128
+ <FlowEngineProvider engine={engine}>
129
+ <AdminLayoutEntryGuard model={model}>
130
+ <div>Mobile layout shell</div>
131
+ </AdminLayoutEntryGuard>
132
+ </FlowEngineProvider>
133
+ ),
134
+ },
135
+ ],
136
+ {
137
+ initialEntries: ['/mobile'],
138
+ },
139
+ );
140
+
141
+ render(<RouterProvider router={router} />);
142
+
143
+ await waitFor(() => {
144
+ expect(request).toHaveBeenCalledWith({
145
+ url: '/desktopRoutes:listAccessible',
146
+ params: {
147
+ tree: true,
148
+ sort: 'sort',
149
+ layout: 'mobile-layout-model',
150
+ },
151
+ });
152
+ });
153
+ expect(screen.getByText('Mobile layout shell')).toBeInTheDocument();
154
+ });
155
+
156
+ it('should keep guard stable when layout getter returns a new object with the same fields', async () => {
157
+ const engine = new FlowEngine();
158
+ const request = vi.fn().mockResolvedValue({
159
+ data: {
160
+ data: [],
161
+ },
162
+ });
163
+ const routeRepository = new RouteRepository({
164
+ api: {
165
+ request,
166
+ resource: vi.fn(),
167
+ },
168
+ } as never);
169
+ const activateLayout = vi.spyOn(routeRepository, 'activateLayout');
170
+ engine.context.defineProperty('routeRepository', {
171
+ value: routeRepository,
172
+ });
173
+ engine.context.defineProperty('app', {
174
+ value: {
175
+ router: {
176
+ getBasename: () => '',
177
+ },
178
+ },
179
+ });
180
+
181
+ const model = {
182
+ get layout() {
183
+ return {
184
+ authCheck: true,
185
+ routeName: 'admin',
186
+ routePath: '/admin',
187
+ uid: 'admin-layout-model',
188
+ };
189
+ },
190
+ } as AdminLayoutModel;
191
+ let forceRerender = () => {};
192
+ const GuardShell = () => {
193
+ const [, setVersion] = React.useState(0);
194
+ forceRerender = () => setVersion((version) => version + 1);
195
+ return (
196
+ <AdminLayoutEntryGuard model={model}>
197
+ <div>Admin layout shell</div>
198
+ </AdminLayoutEntryGuard>
199
+ );
200
+ };
201
+ const router = createMemoryRouter(
202
+ [
203
+ {
204
+ path: '/admin',
205
+ id: 'admin',
206
+ element: (
207
+ <FlowEngineProvider engine={engine}>
208
+ <GuardShell />
209
+ </FlowEngineProvider>
210
+ ),
211
+ },
212
+ ],
213
+ {
214
+ initialEntries: ['/admin'],
215
+ },
216
+ );
217
+
218
+ render(<RouterProvider router={router} />);
219
+
220
+ await waitFor(() => {
221
+ expect(screen.getByText('Admin layout shell')).toBeInTheDocument();
222
+ });
223
+ expect(activateLayout).toHaveBeenCalledTimes(1);
224
+
225
+ await act(async () => {
226
+ forceRerender();
227
+ });
228
+
229
+ expect(activateLayout).toHaveBeenCalledTimes(1);
230
+ expect(request).toHaveBeenCalledTimes(1);
231
+ });
232
+
233
+ it('should reactivate the guard when stable layout fields change', async () => {
234
+ const engine = new FlowEngine();
235
+ const request = vi.fn().mockResolvedValue({
236
+ data: {
237
+ data: [],
238
+ },
239
+ });
240
+ const routeRepository = new RouteRepository({
241
+ api: {
242
+ request,
243
+ resource: vi.fn(),
244
+ },
245
+ } as never);
246
+ const activateLayout = vi.spyOn(routeRepository, 'activateLayout');
247
+ engine.context.defineProperty('routeRepository', {
248
+ value: routeRepository,
249
+ });
250
+ engine.context.defineProperty('app', {
251
+ value: {
252
+ router: {
253
+ getBasename: () => '',
254
+ },
255
+ },
256
+ });
257
+
258
+ let layoutUid = 'admin-layout-model';
259
+ const model = {
260
+ get layout() {
261
+ return {
262
+ authCheck: true,
263
+ routeName: 'admin',
264
+ routePath: '/admin',
265
+ uid: layoutUid,
266
+ };
267
+ },
268
+ } as AdminLayoutModel;
269
+ let forceRerender = () => {};
270
+ const GuardShell = () => {
271
+ const [, setVersion] = React.useState(0);
272
+ forceRerender = () => setVersion((version) => version + 1);
273
+ return (
274
+ <AdminLayoutEntryGuard model={model}>
275
+ <div>Admin layout shell</div>
276
+ </AdminLayoutEntryGuard>
277
+ );
278
+ };
279
+ const router = createMemoryRouter(
280
+ [
281
+ {
282
+ path: '/admin',
283
+ id: 'admin',
284
+ element: (
285
+ <FlowEngineProvider engine={engine}>
286
+ <GuardShell />
287
+ </FlowEngineProvider>
288
+ ),
289
+ },
290
+ ],
291
+ {
292
+ initialEntries: ['/admin'],
293
+ },
294
+ );
295
+
296
+ render(<RouterProvider router={router} />);
297
+
298
+ await waitFor(() => {
299
+ expect(screen.getByText('Admin layout shell')).toBeInTheDocument();
300
+ });
301
+ expect(activateLayout).toHaveBeenCalledWith(expect.objectContaining({ uid: 'admin-layout-model' }));
302
+
303
+ await act(async () => {
304
+ layoutUid = 'admin-layout-next';
305
+ forceRerender();
306
+ });
307
+
308
+ expect(activateLayout).toHaveBeenCalledTimes(2);
309
+ expect(activateLayout).toHaveBeenLastCalledWith(expect.objectContaining({ uid: 'admin-layout-next' }));
310
+ });
311
+
312
+ it('should redirect group id paths to the first v2 landing route', async () => {
313
+ const modernWindow = window as Window & { __nocobase_modern_client_prefix__?: string };
314
+ modernWindow.__nocobase_modern_client_prefix__ = 'v2';
315
+ const engine = new FlowEngine();
316
+ const request = vi.fn().mockResolvedValue({
317
+ data: {
318
+ data: [],
319
+ },
320
+ });
321
+ const routeRepository = new RouteRepository({
322
+ api: {
323
+ request,
324
+ resource: vi.fn(),
325
+ },
326
+ } as never);
327
+ routeRepository.setRoutes([
328
+ {
329
+ id: 1,
330
+ title: 'Group',
331
+ type: NocoBaseDesktopRouteType.group,
332
+ children: [
333
+ {
334
+ id: 11,
335
+ title: 'Flow page',
336
+ schemaUid: 'flow-page-1',
337
+ type: NocoBaseDesktopRouteType.flowPage,
338
+ },
339
+ ],
340
+ },
341
+ ]);
342
+ engine.context.defineProperty('routeRepository', {
343
+ value: routeRepository,
344
+ });
345
+ engine.context.defineProperty('app', {
346
+ value: {
347
+ getPublicPath: () => '/apps/demo/v2/',
348
+ router: {
349
+ getBasename: () => '/apps/demo/v2',
350
+ },
351
+ },
352
+ });
353
+
354
+ const model = {
355
+ layout: {
356
+ routeName: 'admin',
357
+ routePath: '/admin',
358
+ uid: 'admin-layout-model',
359
+ },
360
+ } as AdminLayoutModel;
361
+ const router = createMemoryRouter(
362
+ [
363
+ {
364
+ path: '/admin',
365
+ id: 'admin',
366
+ element: (
367
+ <FlowEngineProvider engine={engine}>
368
+ <AdminLayoutEntryGuard model={model}>
369
+ <Outlet />
370
+ </AdminLayoutEntryGuard>
371
+ </FlowEngineProvider>
372
+ ),
373
+ children: [
374
+ {
375
+ path: ':name',
376
+ id: 'admin.__page',
377
+ element: <div>Admin page shell</div>,
378
+ },
379
+ ],
380
+ },
381
+ ],
382
+ {
383
+ initialEntries: ['/admin/1'],
384
+ },
385
+ );
386
+
387
+ try {
388
+ render(<RouterProvider router={router} />);
389
+
390
+ await waitFor(() => {
391
+ expect(router.state.location.pathname).toBe('/admin/flow-page-1');
392
+ });
393
+ expect(await screen.findByText('Admin page shell')).toBeInTheDocument();
394
+ } finally {
395
+ delete modernWindow.__nocobase_modern_client_prefix__;
396
+ }
397
+ });
398
+
399
+ it('should prefer schema uid routes before resolving numeric group ids', async () => {
400
+ const modernWindow = window as Window & { __nocobase_modern_client_prefix__?: string };
401
+ modernWindow.__nocobase_modern_client_prefix__ = 'v2';
402
+ const engine = new FlowEngine();
403
+ const routeRepository = {
404
+ activateLayout: vi.fn(() => vi.fn()),
405
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
406
+ isAccessibleLoaded: vi.fn(() => true),
407
+ getRouteBySchemaUid: vi.fn((pageUid: string) =>
408
+ pageUid === '1'
409
+ ? {
410
+ schemaUid: '1',
411
+ type: NocoBaseDesktopRouteType.flowPage,
412
+ }
413
+ : undefined,
414
+ ),
415
+ getRouteById: vi.fn((routeId: string) =>
416
+ routeId === '1'
417
+ ? {
418
+ id: 1,
419
+ type: NocoBaseDesktopRouteType.group,
420
+ children: [
421
+ {
422
+ schemaUid: 'flow-page-1',
423
+ type: NocoBaseDesktopRouteType.flowPage,
424
+ },
425
+ ],
426
+ }
427
+ : undefined,
428
+ ),
429
+ listAccessible: vi.fn(() => []),
430
+ };
431
+ engine.context.defineProperty('routeRepository', {
432
+ value: routeRepository,
433
+ });
434
+ engine.context.defineProperty('app', {
435
+ value: {
436
+ getPublicPath: () => '/apps/demo/v2/',
437
+ router: {
438
+ getBasename: () => '/apps/demo/v2',
439
+ },
440
+ },
441
+ });
442
+
443
+ const model = {
444
+ layout: {
445
+ routeName: 'admin',
446
+ routePath: '/admin',
447
+ uid: 'admin-layout-model',
448
+ },
449
+ } as AdminLayoutModel;
450
+ const router = createMemoryRouter(
451
+ [
452
+ {
453
+ path: '/admin',
454
+ id: 'admin',
455
+ element: (
456
+ <FlowEngineProvider engine={engine}>
457
+ <AdminLayoutEntryGuard model={model}>
458
+ <Outlet />
459
+ </AdminLayoutEntryGuard>
460
+ </FlowEngineProvider>
461
+ ),
462
+ children: [
463
+ {
464
+ path: ':name',
465
+ id: 'admin.__page',
466
+ element: <div>Admin page shell</div>,
467
+ },
468
+ ],
469
+ },
470
+ ],
471
+ {
472
+ initialEntries: ['/admin/1'],
473
+ },
474
+ );
475
+
476
+ try {
477
+ render(<RouterProvider router={router} />);
478
+
479
+ await screen.findByText('Admin page shell');
480
+ expect(router.state.location.pathname).toBe('/admin/1');
481
+ expect(routeRepository.getRouteById).not.toHaveBeenCalled();
482
+ } finally {
483
+ delete modernWindow.__nocobase_modern_client_prefix__;
484
+ }
485
+ });
486
+ });