@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +21 -6
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/TypedVariableInput.d.ts +22 -4
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +2 -0
- package/es/flow/index.d.ts +1 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +233 -137
- package/lib/index.js +234 -138
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/TypedVariableInput.tsx +416 -93
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +266 -9
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +2 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +104 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +10 -0
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/openView.tsx +38 -4
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +34 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +82 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +10 -5
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +2 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +12 -1
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +49 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +100 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +8 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +44 -14
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
|
@@ -58,6 +58,16 @@ export interface CollectionFilterItemProps {
|
|
|
58
58
|
t?: (key: string) => string;
|
|
59
59
|
/** Optional v2 app registry used to resolve plugin-provided operator components. */
|
|
60
60
|
app?: { getComponent?: (name: string) => React.ComponentType<any> | undefined };
|
|
61
|
+
/** Optional override for the left field picker placeholder. */
|
|
62
|
+
fieldPlaceholder?: string;
|
|
63
|
+
/** Optional override for the operator picker placeholder. */
|
|
64
|
+
operatorPlaceholder?: string;
|
|
65
|
+
/** Optional override for the value input placeholder. Pass `null` to suppress it. */
|
|
66
|
+
valuePlaceholder?: string | null;
|
|
67
|
+
/** Optional override for the left field picker width. Defaults to v2's original 200px. */
|
|
68
|
+
fieldWidth?: number;
|
|
69
|
+
/** Optional override for the operator picker min-width. Defaults to v2's original 120px. */
|
|
70
|
+
operatorMinWidth?: number;
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
const identity = (s: string) => s;
|
|
@@ -98,6 +108,12 @@ export const CollectionFilterItem: FC<CollectionFilterItemProps> = observer(
|
|
|
98
108
|
const { path: leftValue, operator, value: rightValue } = props.value;
|
|
99
109
|
const flowEngine = useFlowEngine({ throwError: false }) as any;
|
|
100
110
|
const app = props.app || flowEngine?.context?.app;
|
|
111
|
+
const fieldPlaceholder = props.fieldPlaceholder ?? t('Select field');
|
|
112
|
+
const operatorPlaceholder = props.operatorPlaceholder ?? t('Comparision');
|
|
113
|
+
const valuePlaceholder =
|
|
114
|
+
props.valuePlaceholder === undefined ? t('Enter value') : props.valuePlaceholder || undefined;
|
|
115
|
+
const fieldWidth = props.fieldWidth ?? 200;
|
|
116
|
+
const operatorMinWidth = props.operatorMinWidth ?? 120;
|
|
101
117
|
|
|
102
118
|
const options = useFilterOptions(collection, { filterableFieldNames, nonfilterableFieldNames, noIgnore, t });
|
|
103
119
|
|
|
@@ -135,8 +151,8 @@ export const CollectionFilterItem: FC<CollectionFilterItemProps> = observer(
|
|
|
135
151
|
return (
|
|
136
152
|
<Space wrap>
|
|
137
153
|
<Cascader
|
|
138
|
-
style={{ width:
|
|
139
|
-
placeholder={
|
|
154
|
+
style={{ width: fieldWidth }}
|
|
155
|
+
placeholder={fieldPlaceholder}
|
|
140
156
|
options={cascaderOptions}
|
|
141
157
|
value={fieldPath}
|
|
142
158
|
onChange={handleFieldChange}
|
|
@@ -145,15 +161,15 @@ export const CollectionFilterItem: FC<CollectionFilterItemProps> = observer(
|
|
|
145
161
|
popupClassName={cascaderPopupClass}
|
|
146
162
|
/>
|
|
147
163
|
<Select
|
|
148
|
-
style={{
|
|
149
|
-
placeholder={
|
|
164
|
+
style={{ minWidth: operatorMinWidth }}
|
|
165
|
+
placeholder={operatorPlaceholder}
|
|
150
166
|
value={operator || undefined}
|
|
151
167
|
onChange={handleOperatorChange}
|
|
152
168
|
disabled={!leftValue || operatorOptions.length === 0}
|
|
153
169
|
>
|
|
154
170
|
{operatorOptions.map((op) => (
|
|
155
171
|
<Select.Option key={op.value} value={op.value}>
|
|
156
|
-
{op.label}
|
|
172
|
+
{typeof op.label === 'string' ? t(op.label) : op.label}
|
|
157
173
|
</Select.Option>
|
|
158
174
|
))}
|
|
159
175
|
</Select>
|
|
@@ -162,7 +178,7 @@ export const CollectionFilterItem: FC<CollectionFilterItemProps> = observer(
|
|
|
162
178
|
operator={selectedOperator}
|
|
163
179
|
value={rightValue}
|
|
164
180
|
onChange={handleValueChange}
|
|
165
|
-
placeholder={
|
|
181
|
+
placeholder={valuePlaceholder}
|
|
166
182
|
t={t}
|
|
167
183
|
app={app}
|
|
168
184
|
/>
|
|
@@ -179,7 +195,16 @@ export function createCollectionFilterItem(
|
|
|
179
195
|
collection: Collection,
|
|
180
196
|
bound?: Pick<
|
|
181
197
|
CollectionFilterItemProps,
|
|
182
|
-
|
|
198
|
+
| 'filterableFieldNames'
|
|
199
|
+
| 'nonfilterableFieldNames'
|
|
200
|
+
| 'noIgnore'
|
|
201
|
+
| 't'
|
|
202
|
+
| 'app'
|
|
203
|
+
| 'fieldPlaceholder'
|
|
204
|
+
| 'operatorPlaceholder'
|
|
205
|
+
| 'valuePlaceholder'
|
|
206
|
+
| 'fieldWidth'
|
|
207
|
+
| 'operatorMinWidth'
|
|
183
208
|
>,
|
|
184
209
|
) {
|
|
185
210
|
const Component: FC<{ value: CollectionFilterItemValue }> = (props) => (
|
|
@@ -244,4 +244,42 @@ describe('CollectionFilterItem', () => {
|
|
|
244
244
|
expect(value.path).toBe('username');
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
|
+
|
|
248
|
+
it('keeps default widths at 200/120 and honours explicit width/placeholder overrides', () => {
|
|
249
|
+
const value = observable({ path: 'username', operator: '$eq', value: '' });
|
|
250
|
+
const collection = buildStubCollection([{ name: 'username', title: 'Username' }]);
|
|
251
|
+
|
|
252
|
+
const { container, rerender } = render(<CollectionFilterItem value={value} collection={collection} />);
|
|
253
|
+
|
|
254
|
+
let selects = container.querySelectorAll('.ant-select');
|
|
255
|
+
expect(selects[0]).toHaveStyle({ width: '200px' });
|
|
256
|
+
expect(selects[1]).toHaveStyle({ minWidth: '120px' });
|
|
257
|
+
|
|
258
|
+
rerender(
|
|
259
|
+
<CollectionFilterItem
|
|
260
|
+
value={observable({ path: '', operator: '', value: '' })}
|
|
261
|
+
collection={collection}
|
|
262
|
+
fieldWidth={160}
|
|
263
|
+
operatorMinWidth={110}
|
|
264
|
+
fieldPlaceholder="Choose field"
|
|
265
|
+
operatorPlaceholder="Choose operator"
|
|
266
|
+
/>,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
selects = container.querySelectorAll('.ant-select');
|
|
270
|
+
expect(selects[0]).toHaveStyle({ width: '160px' });
|
|
271
|
+
expect(selects[1]).toHaveStyle({ minWidth: '110px' });
|
|
272
|
+
expect(screen.getByText('Choose field')).toBeInTheDocument();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('lets callers suppress the fallback value placeholder', () => {
|
|
276
|
+
const value = observable({ path: 'username', operator: '$eq', value: '' });
|
|
277
|
+
const collection = buildStubCollection([{ name: 'username', title: 'Username' }]);
|
|
278
|
+
|
|
279
|
+
const { rerender } = render(<CollectionFilterItem value={value} collection={collection} />);
|
|
280
|
+
expect(screen.getByPlaceholderText('Enter value')).toBeInTheDocument();
|
|
281
|
+
|
|
282
|
+
rerender(<CollectionFilterItem value={value} collection={collection} valuePlaceholder={null} />);
|
|
283
|
+
expect(screen.queryByPlaceholderText('Enter value')).not.toBeInTheDocument();
|
|
284
|
+
});
|
|
247
285
|
});
|
|
@@ -11,5 +11,7 @@
|
|
|
11
11
|
export { CollectionFilter } from './CollectionFilter';
|
|
12
12
|
export type { CollectionFilterProps } from './CollectionFilter';
|
|
13
13
|
export { CollectionFilterPanel } from './CollectionFilterPanel';
|
|
14
|
+
export { CollectionFilterItem } from './CollectionFilterItem';
|
|
15
|
+
export type { CollectionFilterItemValue } from './CollectionFilterItem';
|
|
14
16
|
export type { CollectionFilterPanelProps, CollectionFilterPanelRef } from './CollectionFilterPanel';
|
|
15
17
|
export type { CompiledFilter } from './useFilterActionProps';
|
package/src/components/index.ts
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
export * from './AppComponents';
|
|
11
11
|
export * from './BlankComponent';
|
|
12
|
+
export * from './category-tabs';
|
|
12
13
|
export * from './form/table/dnd';
|
|
13
14
|
export * from './form';
|
|
14
15
|
export * from './Icon';
|
|
16
|
+
export * from './KeepAlive';
|
|
15
17
|
export * from './PoweredBy';
|
|
16
18
|
export * from './RouterContextCleaner';
|
|
17
19
|
export * from './SwitchLanguage';
|
|
@@ -13,6 +13,7 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
|
13
13
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
14
14
|
import { FlowContextProvider, FlowEngine, FlowEngineProvider, type FlowModel } from '@nocobase/flow-engine';
|
|
15
15
|
import FlowRoute from '../components/FlowRoute';
|
|
16
|
+
import { RouteRepository } from '../../RouteRepository';
|
|
16
17
|
|
|
17
18
|
type MockAdminLayoutModel = FlowModel & {
|
|
18
19
|
registerRoutePage: ReturnType<typeof vi.fn>;
|
|
@@ -321,6 +322,94 @@ describe('FlowRoute', () => {
|
|
|
321
322
|
});
|
|
322
323
|
});
|
|
323
324
|
|
|
325
|
+
it('should ensure accessible routes for the current layout before bridging', async () => {
|
|
326
|
+
const engine = new FlowEngine();
|
|
327
|
+
const request = vi.fn().mockResolvedValue({
|
|
328
|
+
data: {
|
|
329
|
+
data: [
|
|
330
|
+
{
|
|
331
|
+
type: 'flowPage',
|
|
332
|
+
schemaUid: 'mobile-page',
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
const routeRepository = new RouteRepository({
|
|
338
|
+
api: {
|
|
339
|
+
request,
|
|
340
|
+
resource: vi.fn(),
|
|
341
|
+
},
|
|
342
|
+
} as never);
|
|
343
|
+
routeRepository.setRoutes([], 'admin-layout-model');
|
|
344
|
+
const deactivateLayout = routeRepository.activateLayout({
|
|
345
|
+
uid: 'mobile-layout-model',
|
|
346
|
+
});
|
|
347
|
+
engine.context.defineProperty('routeRepository', {
|
|
348
|
+
value: routeRepository,
|
|
349
|
+
});
|
|
350
|
+
engine.context.defineProperty('app', {
|
|
351
|
+
value: {
|
|
352
|
+
getPublicPath: () => '/v2/',
|
|
353
|
+
router: {
|
|
354
|
+
getBasename: () => '/v2',
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
const routeModel = engine.createModel({
|
|
359
|
+
uid: 'mobile-route-model',
|
|
360
|
+
use: 'FlowModel',
|
|
361
|
+
});
|
|
362
|
+
routeModel.context.defineProperty('layout', {
|
|
363
|
+
value: {
|
|
364
|
+
routeName: 'mobile',
|
|
365
|
+
routePath: '/mobile',
|
|
366
|
+
rootRouteName: 'mobile',
|
|
367
|
+
uid: 'mobile-layout-model',
|
|
368
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
369
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
370
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
371
|
+
authCheck: true,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const mobileLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
376
|
+
engine.createModel({
|
|
377
|
+
uid: 'mobile-layout-model',
|
|
378
|
+
use: 'FlowModel',
|
|
379
|
+
}),
|
|
380
|
+
{
|
|
381
|
+
registerRoutePage: vi.fn(),
|
|
382
|
+
updateRoutePage: vi.fn(),
|
|
383
|
+
unregisterRoutePage: vi.fn(),
|
|
384
|
+
},
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
render(
|
|
388
|
+
<FlowEngineProvider engine={engine}>
|
|
389
|
+
<FlowContextProvider context={routeModel.context}>
|
|
390
|
+
<MemoryRouter initialEntries={['/mobile/mobile-page']}>
|
|
391
|
+
<Routes>
|
|
392
|
+
<Route path="/mobile/:name" element={<FlowRoute />} />
|
|
393
|
+
</Routes>
|
|
394
|
+
</MemoryRouter>
|
|
395
|
+
</FlowContextProvider>
|
|
396
|
+
</FlowEngineProvider>,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
await waitFor(() => {
|
|
400
|
+
expect(request).toHaveBeenCalledWith({
|
|
401
|
+
url: '/desktopRoutes:listAccessible',
|
|
402
|
+
params: {
|
|
403
|
+
tree: true,
|
|
404
|
+
sort: 'sort',
|
|
405
|
+
layout: 'mobile-layout-model',
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
expect(mobileLayoutModel.registerRoutePage).toHaveBeenCalledWith('mobile-page', expect.any(Object));
|
|
409
|
+
});
|
|
410
|
+
deactivateLayout();
|
|
411
|
+
});
|
|
412
|
+
|
|
324
413
|
it('should fail fast when admin-layout-model is missing', () => {
|
|
325
414
|
const engine = new FlowEngine();
|
|
326
415
|
engine.context.defineProperty('route', {
|
|
@@ -420,6 +509,75 @@ describe('FlowRoute', () => {
|
|
|
420
509
|
});
|
|
421
510
|
});
|
|
422
511
|
|
|
512
|
+
it('should not rerun accessible guard when layout reference changes but fields stay the same', async () => {
|
|
513
|
+
const engine = new FlowEngine();
|
|
514
|
+
const ensureAccessibleLoaded = vi.fn().mockResolvedValue([]);
|
|
515
|
+
const getRouteBySchemaUid = vi.fn(() => ({ type: 'flowPage', schemaUid: 'test-page' }));
|
|
516
|
+
engine.context.defineProperty('routeRepository', {
|
|
517
|
+
value: {
|
|
518
|
+
refreshAccessible: hookState.refresh,
|
|
519
|
+
isAccessibleLoaded: () => true,
|
|
520
|
+
ensureAccessibleLoaded,
|
|
521
|
+
getRouteBySchemaUid,
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
engine.context.defineProperty('app', {
|
|
525
|
+
value: {
|
|
526
|
+
getPublicPath: () => '/v2/',
|
|
527
|
+
router: {
|
|
528
|
+
getBasename: () => '/v2',
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
534
|
+
engine.createModel({ uid: 'legacy-admin-layout-model', use: 'FlowModel' }),
|
|
535
|
+
{
|
|
536
|
+
registerRoutePage: vi.fn(),
|
|
537
|
+
updateRoutePage: vi.fn(),
|
|
538
|
+
unregisterRoutePage: vi.fn(),
|
|
539
|
+
},
|
|
540
|
+
);
|
|
541
|
+
let layoutReadCount = 0;
|
|
542
|
+
const stableLayout = {
|
|
543
|
+
uid: 'admin-layout-model',
|
|
544
|
+
routeName: 'admin',
|
|
545
|
+
routePath: '/admin',
|
|
546
|
+
authCheck: true,
|
|
547
|
+
};
|
|
548
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
549
|
+
get: () => {
|
|
550
|
+
layoutReadCount += 1;
|
|
551
|
+
return layoutReadCount <= 2 ? { ...stableLayout } : stableLayout;
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const element = (
|
|
556
|
+
<FlowEngineProvider engine={engine}>
|
|
557
|
+
<MemoryRouter initialEntries={['/flow/test-page']}>
|
|
558
|
+
<Routes>
|
|
559
|
+
<Route path="/flow/:name" element={<FlowRoute getLayoutModel={() => layoutModel as any} />} />
|
|
560
|
+
</Routes>
|
|
561
|
+
</MemoryRouter>
|
|
562
|
+
</FlowEngineProvider>
|
|
563
|
+
);
|
|
564
|
+
const { rerender } = render(element);
|
|
565
|
+
|
|
566
|
+
await waitFor(() => {
|
|
567
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('test-page', expect.any(Object));
|
|
568
|
+
});
|
|
569
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
570
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
571
|
+
|
|
572
|
+
rerender(element);
|
|
573
|
+
|
|
574
|
+
await waitFor(() => {
|
|
575
|
+
expect(layoutModel.updateRoutePage).toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
expect(ensureAccessibleLoaded).not.toHaveBeenCalled();
|
|
578
|
+
expect(getRouteBySchemaUid).toHaveBeenCalledTimes(1);
|
|
579
|
+
});
|
|
580
|
+
|
|
423
581
|
it('should show 404 when current route is a legacy page in v2 runtime', async () => {
|
|
424
582
|
const originalLocation = window.location;
|
|
425
583
|
const replace = vi.fn();
|
|
@@ -571,6 +729,22 @@ describe('FlowRoute', () => {
|
|
|
571
729
|
},
|
|
572
730
|
},
|
|
573
731
|
});
|
|
732
|
+
const routeModel = engine.createModel({
|
|
733
|
+
uid: 'public-form-route-model',
|
|
734
|
+
use: 'FlowModel',
|
|
735
|
+
});
|
|
736
|
+
routeModel.context.defineProperty('layout', {
|
|
737
|
+
value: {
|
|
738
|
+
routeName: 'public-forms',
|
|
739
|
+
routePath: '/public-forms',
|
|
740
|
+
rootRouteName: 'public-forms',
|
|
741
|
+
uid: 'public-form-layout-model',
|
|
742
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
743
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
744
|
+
childPageModelClass: 'ChildPageModel',
|
|
745
|
+
authCheck: false,
|
|
746
|
+
},
|
|
747
|
+
});
|
|
574
748
|
|
|
575
749
|
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
576
750
|
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
@@ -581,13 +755,82 @@ describe('FlowRoute', () => {
|
|
|
581
755
|
},
|
|
582
756
|
);
|
|
583
757
|
|
|
758
|
+
render(
|
|
759
|
+
<FlowEngineProvider engine={engine}>
|
|
760
|
+
<FlowContextProvider context={routeModel.context}>
|
|
761
|
+
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
762
|
+
<Routes>
|
|
763
|
+
<Route
|
|
764
|
+
path="/public-forms/:name"
|
|
765
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
766
|
+
/>
|
|
767
|
+
</Routes>
|
|
768
|
+
</MemoryRouter>
|
|
769
|
+
</FlowContextProvider>
|
|
770
|
+
</FlowEngineProvider>,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
await waitFor(() => {
|
|
774
|
+
expect(layoutModel.registerRoutePage).toHaveBeenCalledWith('public-form-1', expect.any(Object));
|
|
775
|
+
});
|
|
776
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it('should use getLayoutModel layout authCheck when FlowContext has no layout', async () => {
|
|
780
|
+
const engine = new FlowEngine();
|
|
781
|
+
engine.setModelRepository({
|
|
782
|
+
findOne: vi.fn().mockResolvedValue({
|
|
783
|
+
uid: 'public-form-1',
|
|
784
|
+
use: 'FlowModel',
|
|
785
|
+
}),
|
|
786
|
+
save: vi.fn(),
|
|
787
|
+
destroy: vi.fn(),
|
|
788
|
+
} as any);
|
|
789
|
+
engine.context.defineProperty('routeRepository', {
|
|
790
|
+
value: {
|
|
791
|
+
refreshAccessible: hookState.refresh,
|
|
792
|
+
isAccessibleLoaded: () => true,
|
|
793
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
794
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
engine.context.defineProperty('app', {
|
|
798
|
+
value: {
|
|
799
|
+
getPublicPath: () => '/v2/',
|
|
800
|
+
router: {
|
|
801
|
+
getBasename: () => '/v2',
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
807
|
+
engine.createModel({ uid: 'public-form-layout-model', use: 'FlowModel' }),
|
|
808
|
+
{
|
|
809
|
+
registerRoutePage: vi.fn(),
|
|
810
|
+
updateRoutePage: vi.fn(),
|
|
811
|
+
unregisterRoutePage: vi.fn(),
|
|
812
|
+
},
|
|
813
|
+
);
|
|
814
|
+
Object.defineProperty(layoutModel, 'layout', {
|
|
815
|
+
value: {
|
|
816
|
+
routeName: 'public-forms',
|
|
817
|
+
routePath: '/public-forms',
|
|
818
|
+
rootRouteName: 'public-forms',
|
|
819
|
+
uid: 'public-form-layout-model',
|
|
820
|
+
layoutModelClass: 'PublicFormLayoutModel',
|
|
821
|
+
rootPageModelClass: 'PublicFormPageModel',
|
|
822
|
+
childPageModelClass: 'ChildPageModel',
|
|
823
|
+
authCheck: false,
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
|
|
584
827
|
render(
|
|
585
828
|
<FlowEngineProvider engine={engine}>
|
|
586
829
|
<MemoryRouter initialEntries={['/public-forms/public-form-1']}>
|
|
587
830
|
<Routes>
|
|
588
831
|
<Route
|
|
589
832
|
path="/public-forms/:name"
|
|
590
|
-
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
833
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel as any} />}
|
|
591
834
|
/>
|
|
592
835
|
</Routes>
|
|
593
836
|
</MemoryRouter>
|
|
@@ -600,6 +843,202 @@ describe('FlowRoute', () => {
|
|
|
600
843
|
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
601
844
|
});
|
|
602
845
|
|
|
846
|
+
it('should render not found when admin route is not accessible', async () => {
|
|
847
|
+
const engine = new FlowEngine();
|
|
848
|
+
engine.setModelRepository({
|
|
849
|
+
findOne: vi.fn().mockResolvedValue({
|
|
850
|
+
uid: 'admin-denied-page',
|
|
851
|
+
use: 'FlowModel',
|
|
852
|
+
}),
|
|
853
|
+
save: vi.fn(),
|
|
854
|
+
destroy: vi.fn(),
|
|
855
|
+
} as any);
|
|
856
|
+
engine.context.defineProperty('routeRepository', {
|
|
857
|
+
value: {
|
|
858
|
+
refreshAccessible: hookState.refresh,
|
|
859
|
+
isAccessibleLoaded: () => true,
|
|
860
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
861
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
engine.context.defineProperty('app', {
|
|
865
|
+
value: {
|
|
866
|
+
getPublicPath: () => '/v2/',
|
|
867
|
+
router: {
|
|
868
|
+
getBasename: () => '/v2',
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
const adminLayoutModel: MockAdminLayoutModel = Object.assign(
|
|
874
|
+
engine.createModel({ uid: 'admin-layout-model', use: 'FlowModel' }),
|
|
875
|
+
{
|
|
876
|
+
registerRoutePage: vi.fn(),
|
|
877
|
+
updateRoutePage: vi.fn(),
|
|
878
|
+
unregisterRoutePage: vi.fn(),
|
|
879
|
+
},
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
render(
|
|
883
|
+
<FlowEngineProvider engine={engine}>
|
|
884
|
+
<MemoryRouter initialEntries={['/admin/admin-denied-page']}>
|
|
885
|
+
<Routes>
|
|
886
|
+
<Route path="/admin/:name" element={<FlowRoute />} />
|
|
887
|
+
</Routes>
|
|
888
|
+
</MemoryRouter>
|
|
889
|
+
</FlowEngineProvider>,
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
893
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('should not use flowModels:findOne to bridge a missing route for protected layouts', async () => {
|
|
897
|
+
const engine = new FlowEngine();
|
|
898
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
899
|
+
uid: 'mobile-denied-page',
|
|
900
|
+
use: 'FlowModel',
|
|
901
|
+
});
|
|
902
|
+
engine.setModelRepository({
|
|
903
|
+
findOne,
|
|
904
|
+
save: vi.fn(),
|
|
905
|
+
destroy: vi.fn(),
|
|
906
|
+
} as any);
|
|
907
|
+
engine.context.defineProperty('routeRepository', {
|
|
908
|
+
value: {
|
|
909
|
+
refreshAccessible: hookState.refresh,
|
|
910
|
+
isAccessibleLoaded: () => true,
|
|
911
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
912
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
engine.context.defineProperty('app', {
|
|
916
|
+
value: {
|
|
917
|
+
getPublicPath: () => '/v2/',
|
|
918
|
+
router: {
|
|
919
|
+
getBasename: () => '/v2',
|
|
920
|
+
},
|
|
921
|
+
},
|
|
922
|
+
});
|
|
923
|
+
const routeModel = engine.createModel({
|
|
924
|
+
uid: 'mobile-route-model',
|
|
925
|
+
use: 'FlowModel',
|
|
926
|
+
});
|
|
927
|
+
routeModel.context.defineProperty('layout', {
|
|
928
|
+
value: {
|
|
929
|
+
routeName: 'mobile',
|
|
930
|
+
routePath: '/mobile',
|
|
931
|
+
rootRouteName: 'mobile',
|
|
932
|
+
uid: 'mobile-layout-model',
|
|
933
|
+
layoutModelClass: 'MobileLayoutModel',
|
|
934
|
+
rootPageModelClass: 'MobileRootPageModel',
|
|
935
|
+
childPageModelClass: 'MobileChildPageModel',
|
|
936
|
+
authCheck: true,
|
|
937
|
+
},
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
941
|
+
engine.createModel({ uid: 'mobile-layout-model', use: 'FlowModel' }),
|
|
942
|
+
{
|
|
943
|
+
registerRoutePage: vi.fn(),
|
|
944
|
+
updateRoutePage: vi.fn(),
|
|
945
|
+
unregisterRoutePage: vi.fn(),
|
|
946
|
+
},
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
render(
|
|
950
|
+
<FlowEngineProvider engine={engine}>
|
|
951
|
+
<FlowContextProvider context={routeModel.context}>
|
|
952
|
+
<MemoryRouter initialEntries={['/mobile/mobile-denied-page']}>
|
|
953
|
+
<Routes>
|
|
954
|
+
<Route
|
|
955
|
+
path="/mobile/:name"
|
|
956
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
957
|
+
/>
|
|
958
|
+
</Routes>
|
|
959
|
+
</MemoryRouter>
|
|
960
|
+
</FlowContextProvider>
|
|
961
|
+
</FlowEngineProvider>,
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
965
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
966
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
it('should not load a known page uid when a protected custom layout has no accessible route', async () => {
|
|
970
|
+
const engine = new FlowEngine();
|
|
971
|
+
const findOne = vi.fn().mockResolvedValue({
|
|
972
|
+
uid: 'known-denied-custom-page',
|
|
973
|
+
use: 'FlowModel',
|
|
974
|
+
});
|
|
975
|
+
engine.setModelRepository({
|
|
976
|
+
findOne,
|
|
977
|
+
save: vi.fn(),
|
|
978
|
+
destroy: vi.fn(),
|
|
979
|
+
} as any);
|
|
980
|
+
engine.context.defineProperty('routeRepository', {
|
|
981
|
+
value: {
|
|
982
|
+
refreshAccessible: hookState.refresh,
|
|
983
|
+
isAccessibleLoaded: () => true,
|
|
984
|
+
ensureAccessibleLoaded: vi.fn().mockResolvedValue([]),
|
|
985
|
+
getRouteBySchemaUid: vi.fn(() => undefined),
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
engine.context.defineProperty('app', {
|
|
989
|
+
value: {
|
|
990
|
+
getPublicPath: () => '/v2/',
|
|
991
|
+
router: {
|
|
992
|
+
getBasename: () => '/v2',
|
|
993
|
+
},
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
const routeModel = engine.createModel({
|
|
997
|
+
uid: 'custom-layout-route-model',
|
|
998
|
+
use: 'FlowModel',
|
|
999
|
+
});
|
|
1000
|
+
routeModel.context.defineProperty('layout', {
|
|
1001
|
+
value: {
|
|
1002
|
+
routeName: 'admin2',
|
|
1003
|
+
routePath: '/admin2',
|
|
1004
|
+
rootRouteName: 'admin2',
|
|
1005
|
+
uid: 'custom-protected-layout-model',
|
|
1006
|
+
layoutModelClass: 'AdminLayoutModel',
|
|
1007
|
+
rootPageModelClass: 'RootPageModel',
|
|
1008
|
+
childPageModelClass: 'ChildPageModel',
|
|
1009
|
+
authCheck: true,
|
|
1010
|
+
},
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
const layoutModel: MockAdminLayoutModel = Object.assign(
|
|
1014
|
+
engine.createModel({ uid: 'custom-protected-layout-model', use: 'FlowModel' }),
|
|
1015
|
+
{
|
|
1016
|
+
registerRoutePage: vi.fn(),
|
|
1017
|
+
updateRoutePage: vi.fn(),
|
|
1018
|
+
unregisterRoutePage: vi.fn(),
|
|
1019
|
+
},
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
render(
|
|
1023
|
+
<FlowEngineProvider engine={engine}>
|
|
1024
|
+
<FlowContextProvider context={routeModel.context}>
|
|
1025
|
+
<MemoryRouter initialEntries={['/admin2/known-denied-custom-page']}>
|
|
1026
|
+
<Routes>
|
|
1027
|
+
<Route
|
|
1028
|
+
path="/admin2/:name"
|
|
1029
|
+
element={<FlowRoute legacyPageBehavior="notFound" getLayoutModel={() => layoutModel} />}
|
|
1030
|
+
/>
|
|
1031
|
+
</Routes>
|
|
1032
|
+
</MemoryRouter>
|
|
1033
|
+
</FlowContextProvider>
|
|
1034
|
+
</FlowEngineProvider>,
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1038
|
+
expect(findOne).not.toHaveBeenCalled();
|
|
1039
|
+
expect(layoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
1040
|
+
});
|
|
1041
|
+
|
|
603
1042
|
it('should check model existence without occupying the route model uid', async () => {
|
|
604
1043
|
const engine = new FlowEngine();
|
|
605
1044
|
const findOne = vi.fn().mockResolvedValue({
|
|
@@ -817,7 +1256,7 @@ describe('FlowRoute', () => {
|
|
|
817
1256
|
}
|
|
818
1257
|
});
|
|
819
1258
|
|
|
820
|
-
it('should not
|
|
1259
|
+
it('should render not found without redirecting when admin route does not exist', async () => {
|
|
821
1260
|
const originalLocation = window.location;
|
|
822
1261
|
const replace = vi.fn();
|
|
823
1262
|
Object.defineProperty(window, 'location', {
|
|
@@ -866,9 +1305,8 @@ describe('FlowRoute', () => {
|
|
|
866
1305
|
</FlowEngineProvider>,
|
|
867
1306
|
);
|
|
868
1307
|
|
|
869
|
-
await
|
|
870
|
-
|
|
871
|
-
});
|
|
1308
|
+
expect(await screen.findByText('404')).toBeInTheDocument();
|
|
1309
|
+
expect(adminLayoutModel.registerRoutePage).not.toHaveBeenCalled();
|
|
872
1310
|
expect(replace).not.toHaveBeenCalled();
|
|
873
1311
|
} finally {
|
|
874
1312
|
Object.defineProperty(window, 'location', {
|
|
@@ -58,4 +58,11 @@ describe('getKey', () => {
|
|
|
58
58
|
|
|
59
59
|
expect(getKey(a)).not.toBe(getKey(b));
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
it('distinguishes RunJS openView route state overrides', () => {
|
|
63
|
+
const a = createViewItem({ viewUid: 'v', openViewRouteState: { mode: 'drawer' } }, 0);
|
|
64
|
+
const b = createViewItem({ viewUid: 'v', openViewRouteState: { mode: 'dialog' } }, 0);
|
|
65
|
+
|
|
66
|
+
expect(getKey(a)).not.toBe(getKey(b));
|
|
67
|
+
});
|
|
61
68
|
});
|