@nocobase/client-v2 2.2.0-beta.9 → 3.0.0-alpha.1
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/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +1 -0
- package/es/acl/ACLProvider.d.ts +10 -0
- package/es/authRedirect.d.ts +5 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +190 -141
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +17 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +195 -146
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +11 -6
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +185 -6
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +79 -10
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-validation.ts +1 -1
- package/src/components/AttachmentUpload.tsx +275 -0
- 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 +4 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/index.ts +2 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +111 -16
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -57
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +78 -32
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/index.ts +3 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +4 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +110 -70
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -0,0 +1,130 @@
|
|
|
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 { act, fireEvent, render, screen } from '@testing-library/react';
|
|
11
|
+
import { Tabs } from 'antd';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
|
14
|
+
|
|
15
|
+
let IsolatedPageTabBar: (typeof import('../PageModelTabBar'))['FilteredPageTabBar'];
|
|
16
|
+
|
|
17
|
+
function StatefulContent() {
|
|
18
|
+
const [value, setValue] = React.useState('Initial value');
|
|
19
|
+
return <input aria-label="Tab draft" value={value} onChange={(event) => setValue(event.target.value)} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('FilteredPageTabBar module isolation', () => {
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
vi.doMock('rc-tabs/es/TabContext', () => ({ default: React.createContext(undefined) }));
|
|
25
|
+
vi.doMock('rc-tabs/lib/TabContext', () => ({ default: React.createContext(undefined) }));
|
|
26
|
+
({ FilteredPageTabBar: IsolatedPageTabBar } = await import('../PageModelTabBar'));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterAll(() => {
|
|
30
|
+
vi.doUnmock('rc-tabs/es/TabContext');
|
|
31
|
+
vi.doUnmock('rc-tabs/lib/TabContext');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('keeps filtering, focus, and aria behavior independent from direct rc-tabs contexts', () => {
|
|
35
|
+
const onChange = vi.fn();
|
|
36
|
+
const items = [
|
|
37
|
+
{ key: 'tab-visible', label: 'Visible tab', children: 'Visible content' },
|
|
38
|
+
{ key: 'tab-hidden', label: 'Hidden tab', children: 'Hidden content' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
render(
|
|
42
|
+
<Tabs
|
|
43
|
+
activeKey="tab-hidden"
|
|
44
|
+
items={items}
|
|
45
|
+
onChange={onChange}
|
|
46
|
+
renderTabBar={(tabBarProps) => (
|
|
47
|
+
<IsolatedPageTabBar
|
|
48
|
+
hiddenTabKeys={new Set(['tab-hidden'])}
|
|
49
|
+
hiddenActiveTabLabel="Hidden tab"
|
|
50
|
+
items={items}
|
|
51
|
+
tabBarProps={tabBarProps}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
/>,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const visibleTab = screen.getByRole('tab', { name: 'Visible tab' });
|
|
58
|
+
expect(visibleTab).toHaveAttribute('tabindex', '0');
|
|
59
|
+
expect(visibleTab).toHaveAttribute('aria-selected', 'false');
|
|
60
|
+
expect(screen.queryByRole('tab', { name: 'Hidden tab' })).not.toBeInTheDocument();
|
|
61
|
+
expect(screen.getByText('Hidden content')).toBeVisible();
|
|
62
|
+
|
|
63
|
+
const activePanel = screen.getByRole('tabpanel', { name: 'Hidden tab' });
|
|
64
|
+
expect(document.getElementById(activePanel.getAttribute('aria-labelledby') || '')).not.toBeNull();
|
|
65
|
+
|
|
66
|
+
fireEvent.focus(visibleTab);
|
|
67
|
+
fireEvent.keyDown(visibleTab, { code: 'Enter' });
|
|
68
|
+
expect(onChange).toHaveBeenCalledWith('tab-visible');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('keeps the hidden active pane mounted across the isolated navigation rerender', () => {
|
|
72
|
+
const items = [
|
|
73
|
+
{ key: 'tab-visible', label: 'Visible tab', children: 'Visible content' },
|
|
74
|
+
{ key: 'tab-hidden', label: 'Hidden tab', children: <StatefulContent /> },
|
|
75
|
+
];
|
|
76
|
+
const renderTabs = (hiddenTabKeys: Set<string>) => (
|
|
77
|
+
<Tabs
|
|
78
|
+
activeKey="tab-hidden"
|
|
79
|
+
items={items}
|
|
80
|
+
renderTabBar={
|
|
81
|
+
hiddenTabKeys.size > 0
|
|
82
|
+
? (tabBarProps) => (
|
|
83
|
+
<IsolatedPageTabBar
|
|
84
|
+
hiddenTabKeys={hiddenTabKeys}
|
|
85
|
+
hiddenActiveTabLabel="Hidden tab"
|
|
86
|
+
items={items}
|
|
87
|
+
tabBarProps={tabBarProps}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
: undefined
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
const { rerender } = render(renderTabs(new Set()));
|
|
95
|
+
|
|
96
|
+
fireEvent.change(screen.getByRole('textbox', { name: 'Tab draft' }), { target: { value: 'Unsaved value' } });
|
|
97
|
+
rerender(renderTabs(new Set(['tab-hidden'])));
|
|
98
|
+
|
|
99
|
+
expect(screen.queryByRole('tab', { name: 'Hidden tab' })).not.toBeInTheDocument();
|
|
100
|
+
expect(screen.getByRole('textbox', { name: 'Tab draft' })).toHaveValue('Unsaved value');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('keeps hidden entries out of the keyboard roving model', async () => {
|
|
104
|
+
const items = [
|
|
105
|
+
{ key: 'tab-a', label: 'Visible A', children: 'Content A' },
|
|
106
|
+
{ key: 'tab-hidden', label: 'Hidden tab', children: 'Hidden content' },
|
|
107
|
+
{ key: 'tab-b', label: 'Visible B', children: 'Content B' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
render(
|
|
111
|
+
<Tabs
|
|
112
|
+
activeKey="tab-a"
|
|
113
|
+
items={items}
|
|
114
|
+
renderTabBar={(tabBarProps) => (
|
|
115
|
+
<IsolatedPageTabBar hiddenTabKeys={new Set(['tab-hidden'])} items={items} tabBarProps={tabBarProps} />
|
|
116
|
+
)}
|
|
117
|
+
/>,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const firstTab = screen.getByRole('tab', { name: 'Visible A' });
|
|
121
|
+
const secondTab = screen.getByRole('tab', { name: 'Visible B' });
|
|
122
|
+
await act(async () => {
|
|
123
|
+
firstTab.focus();
|
|
124
|
+
fireEvent.keyDown(firstTab, { code: 'ArrowRight' });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
expect(screen.queryByRole('tab', { name: 'Hidden tab' })).not.toBeInTheDocument();
|
|
128
|
+
expect(document.activeElement).toBe(secondTab);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
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 { fireEvent, render, screen } from '@testing-library/react';
|
|
11
|
+
import { Tabs } from 'antd';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
14
|
+
import { FilteredPageTabBar } from '../PageModelTabBar';
|
|
15
|
+
|
|
16
|
+
function StatefulContent() {
|
|
17
|
+
const [value, setValue] = React.useState('Initial value');
|
|
18
|
+
return <input aria-label="Tab draft" value={value} onChange={(event) => setValue(event.target.value)} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderTabs(hiddenTabKeys: Set<string>, onChange?: (activeKey: string) => void, activeKey = 'tab-hidden') {
|
|
22
|
+
const items = [
|
|
23
|
+
{ key: 'tab-visible', label: 'Visible tab', children: 'Visible content' },
|
|
24
|
+
{ key: 'tab-hidden', label: 'Hidden tab', children: <StatefulContent /> },
|
|
25
|
+
];
|
|
26
|
+
return (
|
|
27
|
+
<Tabs
|
|
28
|
+
activeKey={activeKey}
|
|
29
|
+
onChange={onChange}
|
|
30
|
+
items={items}
|
|
31
|
+
renderTabBar={
|
|
32
|
+
hiddenTabKeys.size > 0
|
|
33
|
+
? (tabBarProps) => (
|
|
34
|
+
<FilteredPageTabBar
|
|
35
|
+
hiddenTabKeys={hiddenTabKeys}
|
|
36
|
+
hiddenActiveTabLabel="Hidden tab"
|
|
37
|
+
items={items}
|
|
38
|
+
tabBarProps={tabBarProps}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
: undefined
|
|
42
|
+
}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('FilteredPageTabBar', () => {
|
|
48
|
+
it('removes hidden tabs from the real navigation model while retaining their active pane', () => {
|
|
49
|
+
const hiddenTabKeys = new Set(['tab-hidden']);
|
|
50
|
+
|
|
51
|
+
render(renderTabs(hiddenTabKeys));
|
|
52
|
+
|
|
53
|
+
expect(screen.getByRole('tab', { name: 'Visible tab' })).toBeInTheDocument();
|
|
54
|
+
expect(screen.queryByRole('tab', { name: 'Hidden tab' })).not.toBeInTheDocument();
|
|
55
|
+
expect(screen.getByRole('textbox', { name: 'Tab draft' })).toBeVisible();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('keeps the active pane mounted when its navigation entry becomes hidden', () => {
|
|
59
|
+
const { rerender } = render(renderTabs(new Set()));
|
|
60
|
+
const input = screen.getByRole('textbox', { name: 'Tab draft' });
|
|
61
|
+
fireEvent.change(input, { target: { value: 'Unsaved value' } });
|
|
62
|
+
|
|
63
|
+
rerender(renderTabs(new Set(['tab-hidden'])));
|
|
64
|
+
|
|
65
|
+
expect(screen.queryByRole('tab', { name: 'Hidden tab' })).not.toBeInTheDocument();
|
|
66
|
+
expect(screen.getByRole('textbox', { name: 'Tab draft' })).toHaveValue('Unsaved value');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('keeps visible navigation focusable and gives the hidden active pane an accessible name', () => {
|
|
70
|
+
const onChange = vi.fn();
|
|
71
|
+
render(renderTabs(new Set(['tab-hidden']), onChange));
|
|
72
|
+
|
|
73
|
+
const visibleTab = screen.getByRole('tab', { name: 'Visible tab' });
|
|
74
|
+
expect(visibleTab).toHaveAttribute('tabindex', '0');
|
|
75
|
+
expect(visibleTab).toHaveAttribute('aria-selected', 'false');
|
|
76
|
+
|
|
77
|
+
const activePanel = screen.getByRole('tabpanel');
|
|
78
|
+
expect(activePanel).toHaveAccessibleName('Hidden tab');
|
|
79
|
+
expect(document.getElementById(activePanel.getAttribute('aria-labelledby') || '')).not.toBeNull();
|
|
80
|
+
expect(document.querySelectorAll('[role="tabpanel"]')).toHaveLength(1);
|
|
81
|
+
|
|
82
|
+
fireEvent.focus(visibleTab);
|
|
83
|
+
fireEvent.keyDown(visibleTab, { code: 'Enter' });
|
|
84
|
+
expect(onChange).toHaveBeenCalledWith('tab-visible');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('connects a visible navigation entry to the pane owned by the outer Tabs instance', () => {
|
|
88
|
+
render(renderTabs(new Set(['tab-hidden']), undefined, 'tab-visible'));
|
|
89
|
+
|
|
90
|
+
const visibleTab = screen.getByRole('tab', { name: 'Visible tab' });
|
|
91
|
+
const activePanel = screen.getByRole('tabpanel', { name: 'Visible tab' });
|
|
92
|
+
expect(visibleTab).toHaveAttribute('aria-controls', activePanel.id);
|
|
93
|
+
expect(activePanel).toHaveAttribute('aria-labelledby', visibleTab.id);
|
|
94
|
+
expect(document.querySelectorAll(`#${CSS.escape(visibleTab.id)}`)).toHaveLength(1);
|
|
95
|
+
expect(document.querySelectorAll('[role="tabpanel"]')).toHaveLength(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('does not duplicate panes when a tab item requests forceRender', () => {
|
|
99
|
+
const items = [
|
|
100
|
+
{ key: 'tab-visible', label: 'Visible tab', children: 'Visible content', forceRender: true },
|
|
101
|
+
{ key: 'tab-hidden', label: 'Hidden tab', children: 'Hidden content' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
render(
|
|
105
|
+
<Tabs
|
|
106
|
+
activeKey="tab-visible"
|
|
107
|
+
items={items}
|
|
108
|
+
renderTabBar={(tabBarProps) => (
|
|
109
|
+
<FilteredPageTabBar hiddenTabKeys={new Set(['tab-hidden'])} items={items} tabBarProps={tabBarProps} />
|
|
110
|
+
)}
|
|
111
|
+
/>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const activePanel = screen.getByRole('tabpanel', { name: 'Visible tab' });
|
|
115
|
+
expect(document.querySelectorAll(`#${CSS.escape(activePanel.id)}`)).toHaveLength(1);
|
|
116
|
+
expect(document.querySelectorAll('[role="tabpanel"]')).toHaveLength(1);
|
|
117
|
+
});
|
|
118
|
+
});
|