@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.45

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 (278) hide show
  1. package/es/Application.d.ts +7 -0
  2. package/es/BaseApplication.d.ts +13 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
  5. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
  6. package/es/collection-manager/field-configure.d.ts +80 -0
  7. package/es/collection-manager/field-validation.d.ts +43 -0
  8. package/es/collection-manager/filter-operators/index.d.ts +46 -0
  9. package/es/collection-manager/filter-operators/operators.d.ts +30 -0
  10. package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
  11. package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
  12. package/es/collection-manager/interfaces/collection.d.ts +12 -51
  13. package/es/collection-manager/interfaces/color.d.ts +1 -16
  14. package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
  15. package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
  16. package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
  17. package/es/collection-manager/interfaces/datetime.d.ts +1 -44
  18. package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
  19. package/es/collection-manager/interfaces/email.d.ts +1 -29
  20. package/es/collection-manager/interfaces/id.d.ts +1 -16
  21. package/es/collection-manager/interfaces/index.d.ts +2 -3
  22. package/es/collection-manager/interfaces/input.d.ts +1 -102
  23. package/es/collection-manager/interfaces/integer.d.ts +1 -95
  24. package/es/collection-manager/interfaces/json.d.ts +16 -7
  25. package/es/collection-manager/interfaces/m2m.d.ts +11 -19
  26. package/es/collection-manager/interfaces/m2o.d.ts +11 -19
  27. package/es/collection-manager/interfaces/markdown.d.ts +1 -63
  28. package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
  29. package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
  30. package/es/collection-manager/interfaces/number.d.ts +1 -87
  31. package/es/collection-manager/interfaces/o2m.d.ts +12 -24
  32. package/es/collection-manager/interfaces/obo.d.ts +207 -0
  33. package/es/collection-manager/interfaces/oho.d.ts +207 -0
  34. package/es/collection-manager/interfaces/password.d.ts +1 -56
  35. package/es/collection-manager/interfaces/percent.d.ts +1 -84
  36. package/es/collection-manager/interfaces/phone.d.ts +1 -25
  37. package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
  38. package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
  39. package/es/collection-manager/interfaces/richText.d.ts +1 -63
  40. package/es/collection-manager/interfaces/select.d.ts +12 -44
  41. package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
  42. package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
  43. package/es/collection-manager/interfaces/textarea.d.ts +1 -51
  44. package/es/collection-manager/interfaces/time.d.ts +1 -16
  45. package/es/collection-manager/interfaces/types.d.ts +3 -12
  46. package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
  47. package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
  48. package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
  49. package/es/collection-manager/interfaces/url.d.ts +1 -20
  50. package/es/collection-manager/interfaces/uuid.d.ts +1 -34
  51. package/es/collection-manager/template-fields.d.ts +53 -0
  52. package/es/components/KeepAlive.d.ts +22 -0
  53. package/es/components/RouterBridge.d.ts +9 -0
  54. package/es/components/form/DialogFormLayout.d.ts +5 -29
  55. package/es/components/form/VariableInput.d.ts +53 -2
  56. package/es/components/form/filter/CollectionFilter.d.ts +49 -0
  57. package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
  58. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  59. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  60. package/es/components/form/filter/index.d.ts +11 -0
  61. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  62. package/es/components/form/index.d.ts +1 -0
  63. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  64. package/es/data-source/index.d.ts +9 -0
  65. package/es/flow/FlowPage.d.ts +2 -1
  66. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  67. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  68. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  69. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  70. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  71. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
  72. package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
  73. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
  74. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  75. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  76. package/es/flow/components/FlowRoute.d.ts +10 -1
  77. package/es/flow/components/filter/index.d.ts +2 -0
  78. package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
  79. package/es/flow/index.d.ts +4 -0
  80. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  81. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +84 -0
  82. package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
  83. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  84. package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
  85. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  86. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  87. package/es/flow-compat/passwordUtils.d.ts +1 -1
  88. package/es/index.d.ts +6 -0
  89. package/es/index.mjs +552 -459
  90. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  91. package/es/layout-manager/LayoutManager.d.ts +22 -0
  92. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  93. package/es/layout-manager/index.d.ts +13 -0
  94. package/es/layout-manager/types.d.ts +20 -0
  95. package/es/layout-manager/utils.d.ts +14 -0
  96. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  97. package/es/settings-center/index.d.ts +1 -1
  98. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  99. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  100. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  101. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  102. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  103. package/lib/index.js +552 -459
  104. package/package.json +8 -7
  105. package/src/Application.tsx +51 -12
  106. package/src/BaseApplication.tsx +32 -0
  107. package/src/PluginSettingsManager.ts +1 -1
  108. package/src/RouterManager.tsx +17 -1
  109. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  110. package/src/__tests__/app.test.tsx +17 -1
  111. package/src/__tests__/globalDeps.test.ts +1 -0
  112. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +45 -2
  113. package/src/__tests__/plugin-manager.test.tsx +177 -0
  114. package/src/__tests__/settings-center.test.tsx +24 -2
  115. package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
  116. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
  117. package/src/collection-manager/field-configure.ts +548 -0
  118. package/src/collection-manager/field-validation.ts +195 -0
  119. package/src/collection-manager/filter-operators/index.ts +176 -0
  120. package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
  121. package/src/collection-manager/interfaces/checkbox.ts +2 -9
  122. package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
  123. package/src/collection-manager/interfaces/collection.ts +2 -15
  124. package/src/collection-manager/interfaces/color.ts +2 -2
  125. package/src/collection-manager/interfaces/createdAt.ts +2 -2
  126. package/src/collection-manager/interfaces/createdBy.ts +1 -12
  127. package/src/collection-manager/interfaces/dateOnly.ts +8 -2
  128. package/src/collection-manager/interfaces/datetime.ts +2 -2
  129. package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
  130. package/src/collection-manager/interfaces/email.ts +2 -9
  131. package/src/collection-manager/interfaces/id.ts +1 -2
  132. package/src/collection-manager/interfaces/index.ts +2 -3
  133. package/src/collection-manager/interfaces/input.ts +2 -133
  134. package/src/collection-manager/interfaces/integer.ts +2 -71
  135. package/src/collection-manager/interfaces/json.tsx +17 -11
  136. package/src/collection-manager/interfaces/m2m.tsx +0 -21
  137. package/src/collection-manager/interfaces/m2o.tsx +0 -22
  138. package/src/collection-manager/interfaces/markdown.ts +2 -51
  139. package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
  140. package/src/collection-manager/interfaces/nanoid.ts +2 -2
  141. package/src/collection-manager/interfaces/number.ts +2 -85
  142. package/src/collection-manager/interfaces/o2m.tsx +1 -22
  143. package/src/collection-manager/interfaces/obo.tsx +145 -0
  144. package/src/collection-manager/interfaces/oho.tsx +145 -0
  145. package/src/collection-manager/interfaces/password.ts +2 -44
  146. package/src/collection-manager/interfaces/percent.ts +2 -74
  147. package/src/collection-manager/interfaces/phone.ts +2 -2
  148. package/src/collection-manager/interfaces/properties/index.ts +0 -133
  149. package/src/collection-manager/interfaces/radioGroup.ts +2 -2
  150. package/src/collection-manager/interfaces/richText.ts +2 -51
  151. package/src/collection-manager/interfaces/select.ts +2 -14
  152. package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
  153. package/src/collection-manager/interfaces/tableoid.ts +1 -2
  154. package/src/collection-manager/interfaces/textarea.ts +2 -51
  155. package/src/collection-manager/interfaces/time.ts +2 -2
  156. package/src/collection-manager/interfaces/types.ts +4 -12
  157. package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
  158. package/src/collection-manager/interfaces/updatedAt.ts +2 -2
  159. package/src/collection-manager/interfaces/updatedBy.ts +1 -12
  160. package/src/collection-manager/interfaces/url.ts +2 -4
  161. package/src/collection-manager/interfaces/uuid.ts +2 -2
  162. package/src/collection-manager/template-fields.ts +109 -0
  163. package/src/components/KeepAlive.tsx +131 -0
  164. package/src/components/README.md +90 -6
  165. package/src/components/README.zh-CN.md +90 -7
  166. package/src/components/RouterBridge.tsx +28 -4
  167. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  168. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  169. package/src/components/form/DialogFormLayout.tsx +5 -29
  170. package/src/components/form/VariableInput.tsx +101 -28
  171. package/src/components/form/__tests__/VariableInput.test.ts +85 -0
  172. package/src/components/form/filter/CollectionFilter.tsx +111 -0
  173. package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
  174. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  175. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  176. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
  177. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  178. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  179. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  180. package/src/components/form/filter/index.ts +13 -0
  181. package/src/components/form/filter/useFilterActionProps.ts +203 -0
  182. package/src/components/form/index.tsx +1 -0
  183. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  184. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  185. package/src/data-source/index.ts +10 -0
  186. package/src/flow/FlowPage.tsx +35 -7
  187. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  188. package/src/flow/__tests__/FlowRoute.test.tsx +529 -2
  189. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  190. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  191. package/src/flow/actions/aclCheck.tsx +4 -0
  192. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  193. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  194. package/src/flow/actions/linkageRules.tsx +122 -0
  195. package/src/flow/actions/openView.tsx +28 -4
  196. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  197. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  198. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  199. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  200. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
  201. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  202. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +0 -12
  203. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  204. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  205. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
  206. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +1 -26
  207. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  208. package/src/flow/admin-shell/admin-layout/index.ts +3 -0
  209. package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
  210. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  211. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  212. package/src/flow/components/AdminLayout.tsx +4 -154
  213. package/src/flow/components/FlowRoute.tsx +105 -15
  214. package/src/flow/components/filter/index.ts +3 -0
  215. package/src/flow/components/filter/useFilterOptions.ts +102 -0
  216. package/src/flow/index.ts +4 -0
  217. package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
  218. package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
  219. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +26 -1
  220. package/src/flow/models/base/ActionModel.tsx +8 -1
  221. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  222. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  223. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  224. package/src/flow/models/base/RouteModel.tsx +1 -1
  225. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +63 -2
  226. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +206 -0
  227. package/src/flow/models/blocks/assign-form/index.ts +1 -0
  228. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  229. package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
  230. package/src/flow/models/blocks/form/FormItemModel.tsx +8 -1
  231. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  232. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
  233. package/src/flow/models/blocks/form/submitHandler.ts +8 -1
  234. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  235. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  236. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  237. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  238. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  239. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  240. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  241. package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
  242. package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
  243. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  244. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  245. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  246. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  247. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  248. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  249. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  250. package/src/index.ts +6 -0
  251. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  252. package/src/layout-manager/LayoutManager.tsx +185 -0
  253. package/src/layout-manager/LayoutRoute.tsx +138 -0
  254. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  255. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  256. package/src/layout-manager/index.ts +14 -0
  257. package/src/layout-manager/types.ts +22 -0
  258. package/src/layout-manager/utils.ts +37 -0
  259. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  260. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  261. package/src/settings-center/index.ts +1 -1
  262. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  263. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  264. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  265. package/src/settings-center/plugin-manager/index.tsx +254 -0
  266. package/src/settings-center/plugin-manager/types.ts +35 -0
  267. package/src/settings-center/utils.tsx +8 -1
  268. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  269. package/src/theme/globalStyles.ts +10 -0
  270. package/src/utils/globalDeps.ts +2 -0
  271. package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
  272. package/es/collection-manager/interfaces/o2o.d.ts +0 -621
  273. package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
  274. package/es/collection-manager/interfaces/subTable.d.ts +0 -172
  275. package/src/collection-manager/interfaces/linkTo.ts +0 -120
  276. package/src/collection-manager/interfaces/o2o.tsx +0 -561
  277. package/src/collection-manager/interfaces/subTable.ts +0 -218
  278. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -11,7 +11,7 @@ import React from 'react';
11
11
  import { describe, it, expect, vi, beforeEach } from 'vitest';
12
12
  import { MemoryRouter, Route, Routes } from 'react-router-dom';
13
13
  import { render, screen, waitFor } from '@testing-library/react';
14
- import { FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
14
+ import { FlowContextProvider, FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
15
15
  import FlowRoute from '../components/FlowRoute';
16
16
 
17
17
  type MockAdminLayoutModel = FlowModel & {
@@ -86,7 +86,7 @@ describe('FlowRoute', () => {
86
86
  expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith(
87
87
  'test-page',
88
88
  expect.objectContaining({
89
- active: false,
89
+ active: true,
90
90
  refreshDesktopRoutes: expect.any(Function),
91
91
  layoutContentElement: expect.any(HTMLDivElement),
92
92
  }),
@@ -120,6 +120,200 @@ describe('FlowRoute', () => {
120
120
  expect(adminLayoutModel.unregisterRoutePage).toHaveBeenCalledWith('test-page');
121
121
  });
122
122
 
123
+ it('should sync explicit active state to layout route page', async () => {
124
+ const engine = new FlowEngine();
125
+ engine.context.defineProperty('routeRepository', {
126
+ value: {
127
+ refreshAccessible: hookState.refresh,
128
+ isAccessibleLoaded: () => true,
129
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
130
+ getRouteBySchemaUid: vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' })),
131
+ },
132
+ });
133
+ engine.context.defineProperty('app', {
134
+ value: {
135
+ getPublicPath: () => '/v2/',
136
+ router: {
137
+ getBasename: () => '/v2',
138
+ },
139
+ },
140
+ });
141
+
142
+ const adminLayoutModel: MockAdminLayoutModel = Object.assign(
143
+ engine.createModel({
144
+ uid: 'admin-layout-model',
145
+ use: 'FlowModel',
146
+ }),
147
+ {
148
+ registerRoutePage: vi.fn(),
149
+ updateRoutePage: vi.fn(),
150
+ unregisterRoutePage: vi.fn(),
151
+ },
152
+ );
153
+
154
+ const result = render(
155
+ <FlowEngineProvider engine={engine}>
156
+ <MemoryRouter initialEntries={['/flow/test-page']}>
157
+ <Routes>
158
+ <Route path="/flow/:name" element={<FlowRoute active={false} />} />
159
+ </Routes>
160
+ </MemoryRouter>
161
+ </FlowEngineProvider>,
162
+ );
163
+
164
+ await waitFor(() => {
165
+ expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith(
166
+ 'test-page',
167
+ expect.objectContaining({
168
+ active: false,
169
+ }),
170
+ );
171
+ });
172
+
173
+ result.rerender(
174
+ <FlowEngineProvider engine={engine}>
175
+ <MemoryRouter initialEntries={['/flow/test-page']}>
176
+ <Routes>
177
+ <Route path="/flow/:name" element={<FlowRoute active={true} />} />
178
+ </Routes>
179
+ </MemoryRouter>
180
+ </FlowEngineProvider>,
181
+ );
182
+
183
+ await waitFor(() => {
184
+ expect(adminLayoutModel.updateRoutePage).toHaveBeenCalledWith(
185
+ 'test-page',
186
+ expect.objectContaining({
187
+ active: true,
188
+ }),
189
+ );
190
+ });
191
+ });
192
+
193
+ it('should bridge page lifecycle with explicit pageUid', async () => {
194
+ const engine = new FlowEngine();
195
+ engine.context.defineProperty('routeRepository', {
196
+ value: {
197
+ refreshAccessible: hookState.refresh,
198
+ isAccessibleLoaded: () => true,
199
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
200
+ getRouteBySchemaUid: vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' })),
201
+ },
202
+ });
203
+ engine.context.defineProperty('app', {
204
+ value: {
205
+ getPublicPath: () => '/v2/',
206
+ router: {
207
+ getBasename: () => '/v2',
208
+ },
209
+ },
210
+ });
211
+
212
+ const adminLayoutModel: MockAdminLayoutModel = Object.assign(
213
+ engine.createModel({
214
+ uid: 'admin-layout-model',
215
+ use: 'FlowModel',
216
+ }),
217
+ {
218
+ registerRoutePage: vi.fn(),
219
+ updateRoutePage: vi.fn(),
220
+ unregisterRoutePage: vi.fn(),
221
+ },
222
+ );
223
+
224
+ render(
225
+ <FlowEngineProvider engine={engine}>
226
+ <MemoryRouter initialEntries={['/flow']}>
227
+ <Routes>
228
+ <Route path="/flow" element={<FlowRoute pageUid="test-page" />} />
229
+ </Routes>
230
+ </MemoryRouter>
231
+ </FlowEngineProvider>,
232
+ );
233
+
234
+ await waitFor(() => {
235
+ expect(adminLayoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
236
+ });
237
+ });
238
+
239
+ it('should derive layout model from current layout context when rendered from schema', async () => {
240
+ const engine = new FlowEngine();
241
+ const routeRepository = {
242
+ refreshAccessible: hookState.refresh,
243
+ isAccessibleLoaded: () => true,
244
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
245
+ getRouteBySchemaUid: vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' })),
246
+ };
247
+ engine.context.defineProperty('route', {
248
+ value: {
249
+ params: { name: 'test-page' },
250
+ pathname: '/embed/test-page',
251
+ },
252
+ });
253
+ engine.context.defineProperty('routeRepository', {
254
+ value: routeRepository,
255
+ });
256
+ const routeModel = engine.createModel({
257
+ uid: 'route-model',
258
+ use: 'FlowModel',
259
+ });
260
+ routeModel.context.defineProperty('layout', {
261
+ value: {
262
+ routeName: 'embed',
263
+ routePath: '/embed',
264
+ rootRouteName: 'embed',
265
+ uid: 'embed-layout-model',
266
+ layoutModelClass: 'EmbedLayoutModelV2',
267
+ rootPageModelClass: 'RootPageModel',
268
+ childPageModelClass: 'ChildPageModel',
269
+ authCheck: true,
270
+ },
271
+ });
272
+ engine.context.defineProperty('app', {
273
+ value: {
274
+ getPublicPath: () => '/v2/',
275
+ router: {
276
+ getBasename: () => '/v2',
277
+ },
278
+ },
279
+ });
280
+
281
+ const embedLayoutModel: MockAdminLayoutModel = Object.assign(
282
+ engine.createModel({
283
+ uid: 'embed-layout-model',
284
+ use: 'FlowModel',
285
+ }),
286
+ {
287
+ registerRoutePage: vi.fn(),
288
+ updateRoutePage: vi.fn(),
289
+ unregisterRoutePage: vi.fn(),
290
+ },
291
+ );
292
+
293
+ render(
294
+ <FlowEngineProvider engine={engine}>
295
+ <FlowContextProvider context={routeModel.context}>
296
+ <MemoryRouter initialEntries={['/embed/test-page']}>
297
+ <Routes>
298
+ <Route path="/embed/:name" element={<FlowRoute />} />
299
+ </Routes>
300
+ </MemoryRouter>
301
+ </FlowContextProvider>
302
+ </FlowEngineProvider>,
303
+ );
304
+
305
+ await waitFor(() => {
306
+ expect(embedLayoutModel.registerRoutePage).toHaveBeenCalledWith(
307
+ 'test-page',
308
+ expect.objectContaining({
309
+ active: true,
310
+ refreshDesktopRoutes: expect.any(Function),
311
+ layoutContentElement: expect.any(HTMLDivElement),
312
+ }),
313
+ );
314
+ });
315
+ });
316
+
123
317
  it('should fail fast when admin-layout-model is missing', () => {
124
318
  const engine = new FlowEngine();
125
319
  engine.context.defineProperty('route', {
@@ -283,6 +477,339 @@ describe('FlowRoute', () => {
283
477
  }
284
478
  });
285
479
 
480
+ it('should render not found for legacy page when behavior is notFound', async () => {
481
+ const originalLocation = window.location;
482
+ const replace = vi.fn();
483
+ Object.defineProperty(window, 'location', {
484
+ configurable: true,
485
+ value: {
486
+ ...originalLocation,
487
+ pathname: '/v2/embed/test-page',
488
+ replace,
489
+ },
490
+ });
491
+
492
+ try {
493
+ const engine = new FlowEngine();
494
+ engine.context.defineProperty('routeRepository', {
495
+ value: {
496
+ refreshAccessible: hookState.refresh,
497
+ isAccessibleLoaded: () => true,
498
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
499
+ getRouteBySchemaUid: vi.fn(() => ({ type: 'page', schemaUid: 'test-page' })),
500
+ },
501
+ });
502
+ engine.context.defineProperty('app', {
503
+ value: {
504
+ getPublicPath: () => '/v2/',
505
+ router: {
506
+ getBasename: () => '/v2',
507
+ },
508
+ },
509
+ });
510
+
511
+ const adminLayoutModel: MockAdminLayoutModel = Object.assign(
512
+ engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
513
+ {
514
+ registerRoutePage: vi.fn(),
515
+ updateRoutePage: vi.fn(),
516
+ unregisterRoutePage: vi.fn(),
517
+ },
518
+ );
519
+
520
+ const result = render(
521
+ <FlowEngineProvider engine={engine}>
522
+ <MemoryRouter initialEntries={['/embed/test-page']}>
523
+ <Routes>
524
+ <Route path="/embed/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
525
+ </Routes>
526
+ </MemoryRouter>
527
+ </FlowEngineProvider>,
528
+ );
529
+
530
+ await result.findByText('404');
531
+ expect(replace).not.toHaveBeenCalled();
532
+ expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
533
+ } finally {
534
+ Object.defineProperty(window, 'location', {
535
+ configurable: true,
536
+ value: originalLocation,
537
+ });
538
+ }
539
+ });
540
+
541
+ it('should bridge existing FlowModel when behavior is notFound and routeRepository has no route', async () => {
542
+ const engine = new FlowEngine();
543
+ engine.setModelRepository({
544
+ findOne: vi.fn().mockResolvedValue({
545
+ uid: 'public-form-1',
546
+ use: 'FlowModel',
547
+ }),
548
+ save: vi.fn(),
549
+ destroy: vi.fn(),
550
+ } as any);
551
+ engine.context.defineProperty('routeRepository', {
552
+ value: {
553
+ refreshAccessible: hookState.refresh,
554
+ isAccessibleLoaded: () => true,
555
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
556
+ getRouteBySchemaUid: vi.fn(() => undefined),
557
+ },
558
+ });
559
+ engine.context.defineProperty('app', {
560
+ value: {
561
+ getPublicPath: () => '/v2/',
562
+ router: {
563
+ getBasename: () => '/v2',
564
+ },
565
+ },
566
+ });
567
+
568
+ const layoutModel: MockAdminLayoutModel = Object.assign(
569
+ engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
570
+ {
571
+ registerRoutePage: vi.fn(),
572
+ updateRoutePage: vi.fn(),
573
+ unregisterRoutePage: vi.fn(),
574
+ },
575
+ );
576
+
577
+ render(
578
+ <FlowEngineProvider engine={engine}>
579
+ <MemoryRouter initialEntries={['/public-forms/public-form-1']}>
580
+ <Routes>
581
+ <Route
582
+ path="/public-forms/:name"
583
+ element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
584
+ />
585
+ </Routes>
586
+ </MemoryRouter>
587
+ </FlowEngineProvider>,
588
+ );
589
+
590
+ await waitFor(() => {
591
+ expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
592
+ });
593
+ expect(screen.queryByText('404')).not.toBeInTheDocument();
594
+ });
595
+
596
+ it('should check model existence without occupying the route model uid', async () => {
597
+ const engine = new FlowEngine();
598
+ const findOne = vi.fn().mockResolvedValue({
599
+ uid: 'public-form-1',
600
+ });
601
+ const request = vi.fn().mockResolvedValue({
602
+ data: {
603
+ data: {
604
+ uid: 'public-form-1',
605
+ },
606
+ },
607
+ });
608
+ engine.setModelRepository({
609
+ findOne,
610
+ save: vi.fn(),
611
+ destroy: vi.fn(),
612
+ } as any);
613
+ engine.context.defineProperty('routeRepository', {
614
+ value: {
615
+ refreshAccessible: hookState.refresh,
616
+ isAccessibleLoaded: () => true,
617
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
618
+ getRouteBySchemaUid: vi.fn(() => undefined),
619
+ },
620
+ });
621
+ engine.context.defineProperty('app', {
622
+ value: {
623
+ apiClient: {
624
+ request,
625
+ },
626
+ getPublicPath: () => '/v2/',
627
+ router: {
628
+ getBasename: () => '/v2',
629
+ },
630
+ },
631
+ });
632
+ const routeModel = engine.createModel({
633
+ uid: 'public-form-route-model',
634
+ use: 'FlowModel',
635
+ });
636
+ routeModel.context.defineProperty('layout', {
637
+ value: {
638
+ routeName: 'public-forms',
639
+ routePath: '/public-forms',
640
+ rootRouteName: 'public-forms',
641
+ uid: 'public-form-layout-model',
642
+ layoutModelClass: 'PublicFormLayoutModel',
643
+ rootPageModelClass: 'PublicFormPageModel',
644
+ childPageModelClass: 'ChildPageModel',
645
+ authCheck: false,
646
+ },
647
+ });
648
+
649
+ const layoutModel: MockAdminLayoutModel = Object.assign(
650
+ engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
651
+ {
652
+ registerRoutePage: vi.fn(),
653
+ updateRoutePage: vi.fn(),
654
+ unregisterRoutePage: vi.fn(),
655
+ },
656
+ );
657
+
658
+ render(
659
+ <FlowEngineProvider engine={engine}>
660
+ <FlowContextProvider context={routeModel.context}>
661
+ <MemoryRouter initialEntries={['/public-forms/public-form-1']}>
662
+ <Routes>
663
+ <Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
664
+ </Routes>
665
+ </MemoryRouter>
666
+ </FlowContextProvider>
667
+ </FlowEngineProvider>,
668
+ );
669
+
670
+ await waitFor(() => {
671
+ expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
672
+ });
673
+ expect(findOne).toHaveBeenCalledWith({ uid: 'public-form-1' });
674
+ expect(request).not.toHaveBeenCalled();
675
+ expect(engine.getModel('public-form-1')).toBeUndefined();
676
+ });
677
+
678
+ it('should not skip accessible route loading just because layout authCheck is false', async () => {
679
+ const engine = new FlowEngine();
680
+ const ensureAccessibleLoaded = vi.fn().mockRejectedValue(new Error('cannot load accessible routes'));
681
+ const getRouteBySchemaUid = vi.fn();
682
+ engine.setModelRepository({
683
+ findOne: vi.fn().mockResolvedValue({
684
+ uid: 'public-form-1',
685
+ use: 'FlowModel',
686
+ }),
687
+ save: vi.fn(),
688
+ destroy: vi.fn(),
689
+ } as any);
690
+ engine.context.defineProperty('routeRepository', {
691
+ value: {
692
+ refreshAccessible: hookState.refresh,
693
+ isAccessibleLoaded: () => false,
694
+ ensureAccessibleLoaded,
695
+ getRouteBySchemaUid,
696
+ },
697
+ });
698
+ engine.context.defineProperty('app', {
699
+ value: {
700
+ getPublicPath: () => '/v2/',
701
+ router: {
702
+ getBasename: () => '/v2',
703
+ },
704
+ },
705
+ });
706
+ const routeModel = engine.createModel({
707
+ uid: 'public-form-route-model',
708
+ use: 'FlowModel',
709
+ });
710
+ routeModel.context.defineProperty('layout', {
711
+ value: {
712
+ routeName: 'public-forms',
713
+ routePath: '/public-forms',
714
+ rootRouteName: 'public-forms',
715
+ uid: 'public-form-layout-model',
716
+ layoutModelClass: 'PublicFormLayoutModel',
717
+ rootPageModelClass: 'PublicFormPageModel',
718
+ childPageModelClass: 'ChildPageModel',
719
+ authCheck: false,
720
+ },
721
+ });
722
+
723
+ const layoutModel: MockAdminLayoutModel = Object.assign(
724
+ engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
725
+ {
726
+ registerRoutePage: vi.fn(),
727
+ updateRoutePage: vi.fn(),
728
+ unregisterRoutePage: vi.fn(),
729
+ },
730
+ );
731
+
732
+ render(
733
+ <FlowEngineProvider engine={engine}>
734
+ <FlowContextProvider context={routeModel.context}>
735
+ <MemoryRouter initialEntries={['/public-forms/public-form-1']}>
736
+ <Routes>
737
+ <Route path="/public-forms/:name" element={<FlowRoute legacyPageBehavior="notFound" />} />
738
+ </Routes>
739
+ </MemoryRouter>
740
+ </FlowContextProvider>
741
+ </FlowEngineProvider>,
742
+ );
743
+
744
+ await waitFor(() => {
745
+ expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
746
+ });
747
+ expect(ensureAccessibleLoaded).toHaveBeenCalledTimes(1);
748
+ expect(getRouteBySchemaUid).not.toHaveBeenCalled();
749
+ });
750
+
751
+ it('should bridge legacy page when behavior is bridge', async () => {
752
+ const originalLocation = window.location;
753
+ const replace = vi.fn();
754
+ Object.defineProperty(window, 'location', {
755
+ configurable: true,
756
+ value: {
757
+ ...originalLocation,
758
+ pathname: '/v2/admin/test-page',
759
+ replace,
760
+ },
761
+ });
762
+
763
+ try {
764
+ const engine = new FlowEngine();
765
+ engine.context.defineProperty('routeRepository', {
766
+ value: {
767
+ refreshAccessible: hookState.refresh,
768
+ isAccessibleLoaded: () => true,
769
+ ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
770
+ getRouteBySchemaUid: vi.fn(() => ({ type: 'page', schemaUid: 'test-page' })),
771
+ },
772
+ });
773
+ engine.context.defineProperty('app', {
774
+ value: {
775
+ getPublicPath: () => '/v2/',
776
+ router: {
777
+ getBasename: () => '/v2',
778
+ },
779
+ },
780
+ });
781
+
782
+ const adminLayoutModel: MockAdminLayoutModel = Object.assign(
783
+ engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
784
+ {
785
+ registerRoutePage: vi.fn(),
786
+ updateRoutePage: vi.fn(),
787
+ unregisterRoutePage: vi.fn(),
788
+ },
789
+ );
790
+
791
+ render(
792
+ <FlowEngineProvider engine={engine}>
793
+ <MemoryRouter initialEntries={['/flow/test-page']}>
794
+ <Routes>
795
+ <Route path="/flow/:name" element={<FlowRoute legacyPageBehavior="bridge" />} />
796
+ </Routes>
797
+ </MemoryRouter>
798
+ </FlowEngineProvider>,
799
+ );
800
+
801
+ await waitFor(() => {
802
+ expect(adminLayoutModel.registerRoutePage).toHaveBeenCalled();
803
+ });
804
+ expect(replace).not.toHaveBeenCalled();
805
+ } finally {
806
+ Object.defineProperty(window, 'location', {
807
+ configurable: true,
808
+ value: originalLocation,
809
+ });
810
+ }
811
+ });
812
+
286
813
  it('should not redirect when route does not exist', async () => {
287
814
  const originalLocation = window.location;
288
815
  const replace = vi.fn();