datajunction-ui 0.0.1-rc.9 → 0.0.2-0.dev1
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/.env +2 -0
- package/.prettierignore +3 -1
- package/Makefile +9 -0
- package/cleanup-deps.sh +70 -0
- package/dj-logo.svg +10 -0
- package/package.json +53 -14
- package/public/favicon.ico +0 -0
- package/public/index.html +1 -1
- package/runit.sh +30 -0
- package/runit2.sh +30 -0
- package/src/__tests__/reportWebVitals.test.jsx +44 -0
- package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
- package/src/app/components/AddNodeDropdown.jsx +44 -0
- package/src/app/components/ListGroupItem.jsx +9 -1
- package/src/app/components/NamespaceHeader.jsx +4 -13
- package/src/app/components/NodeListActions.jsx +69 -0
- package/src/app/components/NodeMaterializationDelete.jsx +90 -0
- package/src/app/components/NotificationBell.tsx +223 -0
- package/src/app/components/QueryInfo.jsx +172 -0
- package/src/app/components/Search.jsx +94 -0
- package/src/app/components/Tab.jsx +8 -1
- package/src/app/components/ToggleSwitch.jsx +20 -0
- package/src/app/components/UserMenu.tsx +100 -0
- package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
- package/src/app/components/__tests__/NodeMaterializationDelete.test.jsx +263 -0
- package/src/app/components/__tests__/NotificationBell.test.tsx +302 -0
- package/src/app/components/__tests__/QueryInfo.test.jsx +183 -0
- package/src/app/components/__tests__/Search.test.jsx +307 -0
- package/src/app/components/__tests__/Tab.test.jsx +27 -0
- package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
- package/src/app/components/__tests__/UserMenu.test.tsx +241 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
- package/src/app/components/djgraph/Collapse.jsx +47 -0
- package/src/app/components/djgraph/DJNode.jsx +61 -83
- package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
- package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
- package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
- package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
- package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
- package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
- package/src/app/components/forms/Action.jsx +8 -0
- package/src/app/components/forms/NodeNameField.jsx +64 -0
- package/src/app/components/search.css +17 -0
- package/src/app/constants.js +2 -0
- package/src/app/icons/AddItemIcon.jsx +16 -0
- package/src/app/icons/AlertIcon.jsx +33 -0
- package/src/app/icons/CollapsedIcon.jsx +15 -0
- package/src/app/icons/CommitIcon.jsx +45 -0
- package/src/app/icons/DJLogo.jsx +36 -0
- package/src/app/icons/DeleteIcon.jsx +21 -0
- package/src/app/icons/DiffIcon.jsx +63 -0
- package/src/app/icons/EditIcon.jsx +18 -0
- package/src/app/icons/ExpandedIcon.jsx +15 -0
- package/src/app/icons/EyeIcon.jsx +20 -0
- package/src/app/icons/FilterIcon.jsx +7 -0
- package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
- package/src/app/icons/InvalidIcon.jsx +16 -0
- package/src/app/icons/JupyterExportIcon.jsx +25 -0
- package/src/app/icons/LoadingIcon.jsx +14 -0
- package/src/app/icons/NodeIcon.jsx +49 -0
- package/src/app/icons/NotificationIcon.jsx +27 -0
- package/src/app/icons/PythonIcon.jsx +14 -0
- package/src/app/icons/SettingsIcon.jsx +28 -0
- package/src/app/icons/TableIcon.jsx +14 -0
- package/src/app/icons/ValidIcon.jsx +16 -0
- package/src/app/icons/WrenchIcon.jsx +36 -0
- package/src/app/index.tsx +130 -37
- package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
- package/src/app/pages/AddEditNodePage/ColumnMetadata.jsx +61 -0
- package/src/app/pages/AddEditNodePage/ColumnsMetadataInput.jsx +72 -0
- package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
- package/src/app/pages/AddEditNodePage/CustomMetadataField.jsx +144 -0
- package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
- package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
- package/src/app/pages/AddEditNodePage/ExperimentationExtension.jsx +338 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +64 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
- package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
- package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
- package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
- package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
- package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
- package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
- package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
- package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
- package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +110 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +291 -0
- package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
- package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
- package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
- package/src/app/pages/AddEditNodePage/index.jsx +545 -0
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
- package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
- package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
- package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
- package/src/app/pages/LoginPage/index.jsx +17 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +232 -0
- package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
- package/src/app/pages/NamespacePage/NodeModeSelect.jsx +27 -0
- package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
- package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
- package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
- package/src/app/pages/NamespacePage/__tests__/AddNamespacePopover.test.jsx +283 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +331 -0
- package/src/app/pages/NamespacePage/index.jsx +356 -42
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
- package/src/app/pages/NodePage/AddComplexDimensionLinkPopover.jsx +367 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
- package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
- package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
- package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
- package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
- package/src/app/pages/NodePage/ManageDimensionLinksDialog.jsx +526 -0
- package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +421 -30
- package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
- package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
- package/src/app/pages/NodePage/NodeInfoTab.jsx +346 -49
- package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
- package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
- package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
- package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
- package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
- package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
- package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
- package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
- package/src/app/pages/NodePage/__tests__/AddComplexDimensionLinkPopover.test.jsx +459 -0
- package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
- package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
- package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
- package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +144 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +132 -0
- package/src/app/pages/NodePage/__tests__/ManageDimensionLinksDialog.test.jsx +390 -0
- package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
- package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
- package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +882 -0
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
- package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
- package/src/app/pages/NodePage/index.jsx +190 -44
- package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
- package/src/app/pages/NotificationsPage/Loadable.jsx +6 -0
- package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +287 -0
- package/src/app/pages/NotificationsPage/index.jsx +136 -0
- package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
- package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
- package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
- package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
- package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
- package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
- package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
- package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
- package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
- package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
- package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
- package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
- package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
- package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
- package/src/app/pages/OverviewPage/index.jsx +22 -0
- package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
- package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +112 -0
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
- package/src/app/pages/RegisterTablePage/index.jsx +142 -0
- package/src/app/pages/Root/__tests__/index.test.jsx +44 -0
- package/src/app/pages/Root/index.tsx +92 -10
- package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
- package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
- package/src/app/pages/SettingsPage/CreateServiceAccountModal.jsx +152 -0
- package/src/app/pages/SettingsPage/Loadable.jsx +16 -0
- package/src/app/pages/SettingsPage/NotificationSubscriptionsSection.jsx +189 -0
- package/src/app/pages/SettingsPage/ProfileSection.jsx +41 -0
- package/src/app/pages/SettingsPage/ServiceAccountsSection.jsx +95 -0
- package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +318 -0
- package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +233 -0
- package/src/app/pages/SettingsPage/__tests__/ProfileSection.test.jsx +65 -0
- package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +150 -0
- package/src/app/pages/SettingsPage/__tests__/index.test.jsx +184 -0
- package/src/app/pages/SettingsPage/index.jsx +148 -0
- package/src/app/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/services/DJService.js +1444 -21
- package/src/app/services/__tests__/DJService.test.jsx +2118 -0
- package/src/app/utils/__tests__/date.test.js +198 -0
- package/src/app/utils/date.js +65 -0
- package/src/index.tsx +1 -0
- package/src/mocks/mockNodes.jsx +1477 -0
- package/src/setupTests.ts +31 -1
- package/src/styles/dag.css +117 -5
- package/src/styles/index.css +1028 -31
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +81 -0
- package/src/styles/nav-bar.css +274 -0
- package/src/styles/node-creation.scss +276 -0
- package/src/styles/node-list.css +4 -0
- package/src/styles/overview.css +72 -0
- package/src/styles/settings.css +787 -0
- package/src/styles/sorted-table.css +15 -0
- package/src/styles/styles.scss +44 -0
- package/src/styles/styles.scss.d.ts +9 -0
- package/src/utils/form.jsx +23 -0
- package/webpack.config.js +17 -6
- package/.babelrc +0 -4
- package/.env.local +0 -4
- package/.env.production +0 -1
- package/.github/pull_request_template.md +0 -11
- package/.github/workflows/ci.yml +0 -33
- package/.vscode/extensions.json +0 -7
- package/.vscode/launch.json +0 -15
- package/.vscode/settings.json +0 -25
- package/Dockerfile +0 -7
- package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
- package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
- package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
- package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
|
3
|
+
import Search from '../Search';
|
|
4
|
+
import DJClientContext from '../../providers/djclient';
|
|
5
|
+
|
|
6
|
+
const mockDjClient = {
|
|
7
|
+
DataJunctionAPI: {
|
|
8
|
+
nodeDetails: jest.fn(),
|
|
9
|
+
listTags: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('<Search />', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const mockNodes = [
|
|
19
|
+
{
|
|
20
|
+
name: 'default.test_node',
|
|
21
|
+
display_name: 'Test Node',
|
|
22
|
+
description: 'A test node for testing',
|
|
23
|
+
type: 'transform',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'default.another_node',
|
|
27
|
+
display_name: 'Another Node',
|
|
28
|
+
description: null, // Test null description
|
|
29
|
+
type: 'metric',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'default.long_description_node',
|
|
33
|
+
display_name: 'Long Description',
|
|
34
|
+
description:
|
|
35
|
+
'This is a very long description that exceeds 100 characters and should be truncated to prevent display issues in the search results interface',
|
|
36
|
+
type: 'dimension',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const mockTags = [
|
|
41
|
+
{
|
|
42
|
+
name: 'test_tag',
|
|
43
|
+
display_name: 'Test Tag',
|
|
44
|
+
description: 'A test tag',
|
|
45
|
+
tag_type: 'business',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
it('renders search input', async () => {
|
|
50
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
51
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
|
|
52
|
+
|
|
53
|
+
const { getByPlaceholderText } = render(
|
|
54
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
55
|
+
<Search />
|
|
56
|
+
</DJClientContext.Provider>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(getByPlaceholderText('Search')).toBeInTheDocument();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('fetches and initializes search data on mount', async () => {
|
|
63
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
64
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
|
|
65
|
+
|
|
66
|
+
render(
|
|
67
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
68
|
+
<Search />
|
|
69
|
+
</DJClientContext.Provider>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
await waitFor(() => {
|
|
73
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
74
|
+
expect(mockDjClient.DataJunctionAPI.listTags).toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('displays search results when typing', async () => {
|
|
79
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
80
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
|
|
81
|
+
|
|
82
|
+
const { getByPlaceholderText, getByText } = render(
|
|
83
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
84
|
+
<Search />
|
|
85
|
+
</DJClientContext.Provider>,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const searchInput = getByPlaceholderText('Search');
|
|
93
|
+
fireEvent.change(searchInput, { target: { value: 'test' } });
|
|
94
|
+
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(getByText(/Test Node/)).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('displays nodes with correct URLs', async () => {
|
|
101
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
102
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
|
|
103
|
+
|
|
104
|
+
const { getByPlaceholderText, container } = render(
|
|
105
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
106
|
+
<Search />
|
|
107
|
+
</DJClientContext.Provider>,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
await waitFor(() => {
|
|
111
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const searchInput = getByPlaceholderText('Search');
|
|
115
|
+
fireEvent.change(searchInput, { target: { value: 'node' } });
|
|
116
|
+
|
|
117
|
+
await waitFor(() => {
|
|
118
|
+
const links = container.querySelectorAll('a[href^="/nodes/"]');
|
|
119
|
+
expect(links.length).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('displays tags with correct URLs', async () => {
|
|
124
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue([]);
|
|
125
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(mockTags);
|
|
126
|
+
|
|
127
|
+
const { getByPlaceholderText, container } = render(
|
|
128
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
129
|
+
<Search />
|
|
130
|
+
</DJClientContext.Provider>,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await waitFor(() => {
|
|
134
|
+
expect(mockDjClient.DataJunctionAPI.listTags).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const searchInput = getByPlaceholderText('Search');
|
|
138
|
+
fireEvent.change(searchInput, { target: { value: 'tag' } });
|
|
139
|
+
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
const links = container.querySelectorAll('a[href^="/tags/"]');
|
|
142
|
+
expect(links.length).toBeGreaterThan(0);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('truncates long descriptions', async () => {
|
|
147
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
148
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
|
|
149
|
+
|
|
150
|
+
const { getByPlaceholderText, getByText } = render(
|
|
151
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
152
|
+
<Search />
|
|
153
|
+
</DJClientContext.Provider>,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const searchInput = getByPlaceholderText('Search');
|
|
161
|
+
fireEvent.change(searchInput, { target: { value: 'long' } });
|
|
162
|
+
|
|
163
|
+
await waitFor(() => {
|
|
164
|
+
expect(getByText(/\.\.\./)).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('handles null descriptions', async () => {
|
|
169
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
170
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
|
|
171
|
+
|
|
172
|
+
const { getByPlaceholderText, getByText } = render(
|
|
173
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
174
|
+
<Search />
|
|
175
|
+
</DJClientContext.Provider>,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
await waitFor(() => {
|
|
179
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const searchInput = getByPlaceholderText('Search');
|
|
183
|
+
fireEvent.change(searchInput, { target: { value: 'another' } });
|
|
184
|
+
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(getByText(/Another Node/)).toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('limits search results to 20 items', async () => {
|
|
191
|
+
const manyNodes = Array.from({ length: 30 }, (_, i) => ({
|
|
192
|
+
name: `default.node${i}`,
|
|
193
|
+
display_name: `Node ${i}`,
|
|
194
|
+
description: `Description ${i}`,
|
|
195
|
+
type: 'transform',
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(manyNodes);
|
|
199
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
|
|
200
|
+
|
|
201
|
+
const { getByPlaceholderText, container } = render(
|
|
202
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
203
|
+
<Search />
|
|
204
|
+
</DJClientContext.Provider>,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await waitFor(() => {
|
|
208
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const searchInput = getByPlaceholderText('Search');
|
|
212
|
+
fireEvent.change(searchInput, { target: { value: 'node' } });
|
|
213
|
+
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
const results = container.querySelectorAll('.search-result-item');
|
|
216
|
+
expect(results.length).toBeLessThanOrEqual(20);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('handles error when fetching nodes', async () => {
|
|
221
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
222
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockRejectedValue(
|
|
223
|
+
new Error('Network error'),
|
|
224
|
+
);
|
|
225
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
|
|
226
|
+
|
|
227
|
+
render(
|
|
228
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
229
|
+
<Search />
|
|
230
|
+
</DJClientContext.Provider>,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
await waitFor(() => {
|
|
234
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
235
|
+
'Error fetching nodes or tags:',
|
|
236
|
+
expect.any(Error),
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
consoleErrorSpy.mockRestore();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('prevents form submission', async () => {
|
|
244
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue([]);
|
|
245
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
|
|
246
|
+
|
|
247
|
+
const { container } = render(
|
|
248
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
249
|
+
<Search />
|
|
250
|
+
</DJClientContext.Provider>,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const form = container.querySelector('form');
|
|
254
|
+
|
|
255
|
+
const submitEvent = new Event('submit', {
|
|
256
|
+
bubbles: true,
|
|
257
|
+
cancelable: true,
|
|
258
|
+
});
|
|
259
|
+
const preventDefaultSpy = jest.spyOn(submitEvent, 'preventDefault');
|
|
260
|
+
|
|
261
|
+
form.dispatchEvent(submitEvent);
|
|
262
|
+
|
|
263
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('handles empty tags array', async () => {
|
|
267
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
268
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue(null);
|
|
269
|
+
|
|
270
|
+
const { getByPlaceholderText } = render(
|
|
271
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
272
|
+
<Search />
|
|
273
|
+
</DJClientContext.Provider>,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(mockDjClient.DataJunctionAPI.listTags).toHaveBeenCalled();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Should not throw an error
|
|
281
|
+
const searchInput = getByPlaceholderText('Search');
|
|
282
|
+
expect(searchInput).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('shows description separator correctly', async () => {
|
|
286
|
+
mockDjClient.DataJunctionAPI.nodeDetails.mockResolvedValue(mockNodes);
|
|
287
|
+
mockDjClient.DataJunctionAPI.listTags.mockResolvedValue([]);
|
|
288
|
+
|
|
289
|
+
const { getByPlaceholderText, container } = render(
|
|
290
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
291
|
+
<Search />
|
|
292
|
+
</DJClientContext.Provider>,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
await waitFor(() => {
|
|
296
|
+
expect(mockDjClient.DataJunctionAPI.nodeDetails).toHaveBeenCalled();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const searchInput = getByPlaceholderText('Search');
|
|
300
|
+
fireEvent.change(searchInput, { target: { value: 'test' } });
|
|
301
|
+
|
|
302
|
+
await waitFor(() => {
|
|
303
|
+
const results = container.querySelector('.search-result-item');
|
|
304
|
+
expect(results).toBeInTheDocument();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import Tab from '../Tab';
|
|
5
|
+
|
|
6
|
+
describe('<Tab />', () => {
|
|
7
|
+
it('renders without crashing', () => {
|
|
8
|
+
render(<Tab />);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('has the active class when selectedTab matches id', () => {
|
|
12
|
+
const { container } = render(<Tab id="1" selectedTab="1" />);
|
|
13
|
+
expect(container.querySelector('.col')).toHaveClass('active');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('does not have the active class when selectedTab does not match id', () => {
|
|
17
|
+
const { container } = render(<Tab id="1" selectedTab="2" />);
|
|
18
|
+
expect(container.querySelector('.col')).not.toHaveClass('active');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('calls onClick when the button is clicked', () => {
|
|
22
|
+
const onClickMock = jest.fn();
|
|
23
|
+
const { getByRole } = render(<Tab id="1" onClick={onClickMock} />);
|
|
24
|
+
fireEvent.click(getByRole('button'));
|
|
25
|
+
expect(onClickMock).toHaveBeenCalledTimes(1);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen } from '@testing-library/react';
|
|
3
|
+
import ToggleSwitch from '../ToggleSwitch';
|
|
4
|
+
|
|
5
|
+
describe('<ToggleSwitch />', () => {
|
|
6
|
+
const defaultProps = {
|
|
7
|
+
checked: false,
|
|
8
|
+
onChange: jest.fn(),
|
|
9
|
+
toggleName: 'Toggle Switch',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
it('renders without crashing', () => {
|
|
13
|
+
render(<ToggleSwitch {...defaultProps} />);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('displays the correct toggle name', () => {
|
|
17
|
+
render(<ToggleSwitch {...defaultProps} />);
|
|
18
|
+
expect(screen.getByText(defaultProps.toggleName)).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('reflects the checked state correctly', () => {
|
|
22
|
+
render(<ToggleSwitch {...defaultProps} checked={true} />);
|
|
23
|
+
const checkbox = screen.getByRole('checkbox');
|
|
24
|
+
expect(checkbox).toBeChecked();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('calls onChange with the correct value when toggled', () => {
|
|
28
|
+
render(<ToggleSwitch {...defaultProps} />);
|
|
29
|
+
const checkbox = screen.getByRole('checkbox');
|
|
30
|
+
|
|
31
|
+
fireEvent.click(checkbox);
|
|
32
|
+
expect(defaultProps.onChange).toHaveBeenCalledWith(true);
|
|
33
|
+
|
|
34
|
+
fireEvent.click(checkbox);
|
|
35
|
+
expect(checkbox).not.toBeChecked();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('is unchecked by default if no checked prop is provided', () => {
|
|
39
|
+
render(<ToggleSwitch onChange={jest.fn()} toggleName="Test Toggle" />);
|
|
40
|
+
const checkbox = screen.getByRole('checkbox');
|
|
41
|
+
expect(checkbox).not.toBeChecked();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import UserMenu from '../UserMenu';
|
|
4
|
+
import DJClientContext from '../../providers/djclient';
|
|
5
|
+
|
|
6
|
+
describe('<UserMenu />', () => {
|
|
7
|
+
const createMockDjClient = (overrides = {}) => ({
|
|
8
|
+
whoami: jest.fn().mockResolvedValue({
|
|
9
|
+
id: 1,
|
|
10
|
+
username: 'testuser',
|
|
11
|
+
email: 'test@example.com',
|
|
12
|
+
}),
|
|
13
|
+
logout: jest.fn().mockResolvedValue({}),
|
|
14
|
+
...overrides,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const renderWithContext = (mockDjClient: any, props = {}) => {
|
|
18
|
+
return render(
|
|
19
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
20
|
+
<UserMenu {...props} />
|
|
21
|
+
</DJClientContext.Provider>,
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Mock window.location.reload
|
|
26
|
+
const originalLocation = window.location;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
jest.clearAllMocks();
|
|
30
|
+
delete (window as any).location;
|
|
31
|
+
(window as any).location = { ...originalLocation, reload: jest.fn() };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
(window as any).location = originalLocation;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders the avatar button', async () => {
|
|
39
|
+
const mockDjClient = createMockDjClient();
|
|
40
|
+
renderWithContext(mockDjClient);
|
|
41
|
+
|
|
42
|
+
const button = screen.getByRole('button');
|
|
43
|
+
expect(button).toBeInTheDocument();
|
|
44
|
+
expect(button).toHaveClass('avatar-button');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('shows "?" before user is loaded', () => {
|
|
48
|
+
const mockDjClient = createMockDjClient({
|
|
49
|
+
whoami: jest.fn().mockImplementation(
|
|
50
|
+
() => new Promise(() => {}), // Never resolves
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
renderWithContext(mockDjClient);
|
|
54
|
+
|
|
55
|
+
const button = screen.getByRole('button');
|
|
56
|
+
expect(button).toHaveTextContent('?');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('displays initials from username (first two letters uppercase)', async () => {
|
|
60
|
+
const mockDjClient = createMockDjClient({
|
|
61
|
+
whoami: jest.fn().mockResolvedValue({
|
|
62
|
+
id: 1,
|
|
63
|
+
username: 'johndoe',
|
|
64
|
+
email: 'john@example.com',
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
renderWithContext(mockDjClient);
|
|
68
|
+
|
|
69
|
+
await waitFor(() => {
|
|
70
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const button = await screen.findByText('JO');
|
|
74
|
+
expect(button).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('displays initials from name when available', async () => {
|
|
78
|
+
const mockDjClient = createMockDjClient({
|
|
79
|
+
whoami: jest.fn().mockResolvedValue({
|
|
80
|
+
id: 1,
|
|
81
|
+
username: 'johndoe',
|
|
82
|
+
email: 'john@example.com',
|
|
83
|
+
name: 'John Doe',
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
renderWithContext(mockDjClient);
|
|
87
|
+
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const button = await screen.findByText('JD');
|
|
93
|
+
expect(button).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('opens dropdown when avatar is clicked', async () => {
|
|
97
|
+
const mockDjClient = createMockDjClient();
|
|
98
|
+
renderWithContext(mockDjClient);
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const button = screen.getByRole('button');
|
|
105
|
+
fireEvent.click(button);
|
|
106
|
+
|
|
107
|
+
expect(screen.getByText('testuser')).toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('shows Settings and Logout links in dropdown', async () => {
|
|
111
|
+
const mockDjClient = createMockDjClient();
|
|
112
|
+
renderWithContext(mockDjClient);
|
|
113
|
+
|
|
114
|
+
await waitFor(() => {
|
|
115
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const button = screen.getByRole('button');
|
|
119
|
+
fireEvent.click(button);
|
|
120
|
+
|
|
121
|
+
const settingsLink = screen.getByText('Settings');
|
|
122
|
+
expect(settingsLink).toHaveAttribute('href', '/settings');
|
|
123
|
+
|
|
124
|
+
const logoutLink = screen.getByText('Logout');
|
|
125
|
+
expect(logoutLink).toHaveAttribute('href', '/');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('calls logout and reloads page when Logout is clicked', async () => {
|
|
129
|
+
const mockDjClient = createMockDjClient();
|
|
130
|
+
renderWithContext(mockDjClient);
|
|
131
|
+
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const button = screen.getByRole('button');
|
|
137
|
+
fireEvent.click(button);
|
|
138
|
+
|
|
139
|
+
const logoutLink = screen.getByText('Logout');
|
|
140
|
+
fireEvent.click(logoutLink);
|
|
141
|
+
|
|
142
|
+
expect(mockDjClient.logout).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('calls onDropdownToggle when dropdown is opened', async () => {
|
|
146
|
+
const mockDjClient = createMockDjClient();
|
|
147
|
+
const onDropdownToggle = jest.fn();
|
|
148
|
+
renderWithContext(mockDjClient, { onDropdownToggle });
|
|
149
|
+
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const button = screen.getByRole('button');
|
|
155
|
+
fireEvent.click(button);
|
|
156
|
+
|
|
157
|
+
expect(onDropdownToggle).toHaveBeenCalledWith(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('closes dropdown when forceClose becomes true', async () => {
|
|
161
|
+
const mockDjClient = createMockDjClient();
|
|
162
|
+
|
|
163
|
+
const { rerender } = render(
|
|
164
|
+
<DJClientContext.Provider
|
|
165
|
+
value={{ DataJunctionAPI: mockDjClient as any }}
|
|
166
|
+
>
|
|
167
|
+
<UserMenu forceClose={false} />
|
|
168
|
+
</DJClientContext.Provider>,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
await waitFor(() => {
|
|
172
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Open the dropdown
|
|
176
|
+
const button = screen.getByRole('button');
|
|
177
|
+
fireEvent.click(button);
|
|
178
|
+
|
|
179
|
+
// Verify dropdown is open
|
|
180
|
+
expect(screen.getByText('testuser')).toBeInTheDocument();
|
|
181
|
+
|
|
182
|
+
// Rerender with forceClose=true
|
|
183
|
+
rerender(
|
|
184
|
+
<DJClientContext.Provider
|
|
185
|
+
value={{ DataJunctionAPI: mockDjClient as any }}
|
|
186
|
+
>
|
|
187
|
+
<UserMenu forceClose={true} />
|
|
188
|
+
</DJClientContext.Provider>,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Dropdown should be closed
|
|
192
|
+
expect(screen.queryByText('Settings')).not.toBeInTheDocument();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('closes dropdown when clicking outside', async () => {
|
|
196
|
+
const mockDjClient = createMockDjClient();
|
|
197
|
+
const onDropdownToggle = jest.fn();
|
|
198
|
+
renderWithContext(mockDjClient, { onDropdownToggle });
|
|
199
|
+
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Open the dropdown
|
|
205
|
+
const button = screen.getByRole('button');
|
|
206
|
+
fireEvent.click(button);
|
|
207
|
+
|
|
208
|
+
// Verify dropdown is open
|
|
209
|
+
expect(screen.getByText('testuser')).toBeInTheDocument();
|
|
210
|
+
|
|
211
|
+
// Click outside
|
|
212
|
+
fireEvent.click(document.body);
|
|
213
|
+
|
|
214
|
+
// Dropdown should be closed
|
|
215
|
+
expect(screen.queryByText('Settings')).not.toBeInTheDocument();
|
|
216
|
+
|
|
217
|
+
// onDropdownToggle should be called with false
|
|
218
|
+
expect(onDropdownToggle).toHaveBeenCalledWith(false);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('toggles dropdown closed when clicking avatar again', async () => {
|
|
222
|
+
const mockDjClient = createMockDjClient();
|
|
223
|
+
const onDropdownToggle = jest.fn();
|
|
224
|
+
renderWithContext(mockDjClient, { onDropdownToggle });
|
|
225
|
+
|
|
226
|
+
await waitFor(() => {
|
|
227
|
+
expect(mockDjClient.whoami).toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const button = screen.getByRole('button');
|
|
231
|
+
|
|
232
|
+
// Open
|
|
233
|
+
fireEvent.click(button);
|
|
234
|
+
expect(onDropdownToggle).toHaveBeenCalledWith(true);
|
|
235
|
+
expect(screen.getByText('testuser')).toBeInTheDocument();
|
|
236
|
+
|
|
237
|
+
// Close by clicking again
|
|
238
|
+
fireEvent.click(button);
|
|
239
|
+
expect(onDropdownToggle).toHaveBeenCalledWith(false);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -14,11 +14,16 @@ exports[`<ListGroupItem /> should render and match the snapshot 1`] = `
|
|
|
14
14
|
Name
|
|
15
15
|
</h6>
|
|
16
16
|
<p
|
|
17
|
+
aria-hidden="false"
|
|
18
|
+
aria-label="Name"
|
|
17
19
|
className="mb-0 opacity-75"
|
|
20
|
+
role="dialog"
|
|
18
21
|
>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
<Markdown>
|
|
23
|
+
<span>
|
|
24
|
+
Something
|
|
25
|
+
</span>
|
|
26
|
+
</Markdown>
|
|
22
27
|
</p>
|
|
23
28
|
</div>
|
|
24
29
|
</div>
|
|
@@ -8,25 +8,9 @@ exports[`<NamespaceHeader /> should render and match the snapshot 1`] = `
|
|
|
8
8
|
className="breadcrumb-item"
|
|
9
9
|
>
|
|
10
10
|
<a
|
|
11
|
-
href="/
|
|
11
|
+
href="/"
|
|
12
12
|
>
|
|
13
|
-
<
|
|
14
|
-
className="bi bi-house-door-fill"
|
|
15
|
-
fill="currentColor"
|
|
16
|
-
height="16"
|
|
17
|
-
style={
|
|
18
|
-
Object {
|
|
19
|
-
"paddingBottom": "0.2rem",
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
viewBox="0 0 16 16"
|
|
23
|
-
width="16"
|
|
24
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
25
|
-
>
|
|
26
|
-
<path
|
|
27
|
-
d="M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z"
|
|
28
|
-
/>
|
|
29
|
-
</svg>
|
|
13
|
+
<HorizontalHierarchyIcon />
|
|
30
14
|
</a>
|
|
31
15
|
</li>
|
|
32
16
|
<li
|