datajunction-ui 0.0.74 → 0.0.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/app/components/DashboardCard.jsx +93 -0
  3. package/src/app/components/NodeComponents.jsx +173 -0
  4. package/src/app/components/NodeListActions.jsx +8 -3
  5. package/src/app/components/__tests__/NodeComponents.test.jsx +262 -0
  6. package/src/app/hooks/__tests__/useWorkspaceData.test.js +533 -0
  7. package/src/app/hooks/useWorkspaceData.js +357 -0
  8. package/src/app/index.tsx +6 -0
  9. package/src/app/pages/MyWorkspacePage/ActiveBranchesSection.jsx +344 -0
  10. package/src/app/pages/MyWorkspacePage/CollectionsSection.jsx +188 -0
  11. package/src/app/pages/MyWorkspacePage/Loadable.jsx +6 -0
  12. package/src/app/pages/MyWorkspacePage/MaterializationsSection.jsx +190 -0
  13. package/src/app/pages/MyWorkspacePage/MyNodesSection.jsx +342 -0
  14. package/src/app/pages/MyWorkspacePage/MyWorkspacePage.css +632 -0
  15. package/src/app/pages/MyWorkspacePage/NeedsAttentionSection.jsx +185 -0
  16. package/src/app/pages/MyWorkspacePage/NodeList.jsx +46 -0
  17. package/src/app/pages/MyWorkspacePage/NotificationsSection.jsx +133 -0
  18. package/src/app/pages/MyWorkspacePage/TypeGroupGrid.jsx +209 -0
  19. package/src/app/pages/MyWorkspacePage/__tests__/ActiveBranchesSection.test.jsx +295 -0
  20. package/src/app/pages/MyWorkspacePage/__tests__/CollectionsSection.test.jsx +278 -0
  21. package/src/app/pages/MyWorkspacePage/__tests__/MaterializationsSection.test.jsx +238 -0
  22. package/src/app/pages/MyWorkspacePage/__tests__/MyNodesSection.test.jsx +389 -0
  23. package/src/app/pages/MyWorkspacePage/__tests__/MyWorkspacePage.test.jsx +347 -0
  24. package/src/app/pages/MyWorkspacePage/__tests__/NeedsAttentionSection.test.jsx +272 -0
  25. package/src/app/pages/MyWorkspacePage/__tests__/NodeList.test.jsx +162 -0
  26. package/src/app/pages/MyWorkspacePage/__tests__/NotificationsSection.test.jsx +204 -0
  27. package/src/app/pages/MyWorkspacePage/__tests__/TypeGroupGrid.test.jsx +556 -0
  28. package/src/app/pages/MyWorkspacePage/index.jsx +150 -0
  29. package/src/app/services/DJService.js +323 -2
@@ -0,0 +1,238 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { MaterializationsSection } from '../MaterializationsSection';
5
+
6
+ jest.mock('../MyWorkspacePage.css', () => ({}));
7
+
8
+ describe('<MaterializationsSection />', () => {
9
+ const now = Date.now();
10
+
11
+ const createMockNode = (name, hoursAgo, schedule = '@daily') => ({
12
+ name,
13
+ type: 'CUBE',
14
+ current: {
15
+ displayName: name,
16
+ availability: {
17
+ validThroughTs:
18
+ hoursAgo !== null ? now - hoursAgo * 60 * 60 * 1000 : null,
19
+ table: 'warehouse.materialized_table',
20
+ },
21
+ materializations: [{ name: 'mat1', schedule }],
22
+ },
23
+ });
24
+
25
+ it('should render loading state', () => {
26
+ render(
27
+ <MemoryRouter>
28
+ <MaterializationsSection nodes={[]} loading={true} />
29
+ </MemoryRouter>,
30
+ );
31
+
32
+ expect(
33
+ screen.queryByText('No materializations configured.'),
34
+ ).not.toBeInTheDocument();
35
+ });
36
+
37
+ it('should render empty state when no materializations', () => {
38
+ render(
39
+ <MemoryRouter>
40
+ <MaterializationsSection nodes={[]} loading={false} />
41
+ </MemoryRouter>,
42
+ );
43
+
44
+ expect(
45
+ screen.getByText('No materializations configured.'),
46
+ ).toBeInTheDocument();
47
+ expect(screen.getByText('Materialize a node')).toBeInTheDocument();
48
+ expect(
49
+ screen.getByText('Speed up queries with cached data'),
50
+ ).toBeInTheDocument();
51
+ });
52
+
53
+ it('should render pending status for nodes without validThroughTs', () => {
54
+ const pendingNode = createMockNode('pending_cube', null);
55
+
56
+ render(
57
+ <MemoryRouter>
58
+ <MaterializationsSection nodes={[pendingNode]} loading={false} />
59
+ </MemoryRouter>,
60
+ );
61
+
62
+ expect(screen.getByText('pending_cube')).toBeInTheDocument();
63
+ expect(screen.getByText('⏳ Pending')).toBeInTheDocument();
64
+ });
65
+
66
+ it('should render green status for recent materializations (< 24h)', () => {
67
+ const freshNode = createMockNode('fresh_cube', 12); // 12 hours ago
68
+
69
+ render(
70
+ <MemoryRouter>
71
+ <MaterializationsSection nodes={[freshNode]} loading={false} />
72
+ </MemoryRouter>,
73
+ );
74
+
75
+ expect(screen.getByText('fresh_cube')).toBeInTheDocument();
76
+ expect(screen.getByText(/12h ago/)).toBeInTheDocument();
77
+ });
78
+
79
+ it('should render yellow status for stale materializations (24-72h)', () => {
80
+ const staleNode = createMockNode('stale_cube', 48); // 48 hours ago
81
+
82
+ render(
83
+ <MemoryRouter>
84
+ <MaterializationsSection nodes={[staleNode]} loading={false} />
85
+ </MemoryRouter>,
86
+ );
87
+
88
+ expect(screen.getByText('stale_cube')).toBeInTheDocument();
89
+ expect(screen.getByText(/2d ago/)).toBeInTheDocument();
90
+ });
91
+
92
+ it('should render red status for very stale materializations (> 72h)', () => {
93
+ const veryStaleNode = createMockNode('very_stale_cube', 168); // 7 days ago
94
+
95
+ render(
96
+ <MemoryRouter>
97
+ <MaterializationsSection nodes={[veryStaleNode]} loading={false} />
98
+ </MemoryRouter>,
99
+ );
100
+
101
+ expect(screen.getByText('very_stale_cube')).toBeInTheDocument();
102
+ expect(screen.getByText(/7d ago/)).toBeInTheDocument();
103
+ });
104
+
105
+ it('should sort nodes by validThroughTs (most recent first)', () => {
106
+ const nodes = [
107
+ createMockNode('old_cube', 72),
108
+ createMockNode('recent_cube', 12),
109
+ createMockNode('middle_cube', 36),
110
+ ];
111
+
112
+ render(
113
+ <MemoryRouter>
114
+ <MaterializationsSection nodes={nodes} loading={false} />
115
+ </MemoryRouter>,
116
+ );
117
+
118
+ const allCubes = screen.getAllByText(/cube/);
119
+ // Should be sorted: recent, middle, old
120
+ expect(allCubes[0]).toHaveTextContent('recent_cube');
121
+ expect(allCubes[1]).toHaveTextContent('middle_cube');
122
+ expect(allCubes[2]).toHaveTextContent('old_cube');
123
+ });
124
+
125
+ it('should display materialization schedule', () => {
126
+ const node = createMockNode('scheduled_cube', 12, '@hourly');
127
+
128
+ render(
129
+ <MemoryRouter>
130
+ <MaterializationsSection nodes={[node]} loading={false} />
131
+ </MemoryRouter>,
132
+ );
133
+
134
+ expect(screen.getByText('🕐 @hourly')).toBeInTheDocument();
135
+ });
136
+
137
+ it('should display materialization table', () => {
138
+ const node = createMockNode('cube_with_table', 12);
139
+
140
+ render(
141
+ <MemoryRouter>
142
+ <MaterializationsSection nodes={[node]} loading={false} />
143
+ </MemoryRouter>,
144
+ );
145
+
146
+ expect(
147
+ screen.getByText('→ warehouse.materialized_table'),
148
+ ).toBeInTheDocument();
149
+ });
150
+
151
+ it('should handle nodes with no schedule', () => {
152
+ const nodeWithoutSchedule = {
153
+ name: 'no_schedule_cube',
154
+ type: 'CUBE',
155
+ current: {
156
+ displayName: 'no_schedule_cube',
157
+ availability: {
158
+ validThroughTs: now - 12 * 60 * 60 * 1000,
159
+ },
160
+ materializations: [{ name: 'mat1' }], // No schedule
161
+ },
162
+ };
163
+
164
+ render(
165
+ <MemoryRouter>
166
+ <MaterializationsSection
167
+ nodes={[nodeWithoutSchedule]}
168
+ loading={false}
169
+ />
170
+ </MemoryRouter>,
171
+ );
172
+
173
+ expect(screen.getByText('🕐 No schedule')).toBeInTheDocument();
174
+ });
175
+
176
+ it('should limit display to 5 nodes', () => {
177
+ const manyNodes = Array.from({ length: 10 }, (_, i) =>
178
+ createMockNode(`cube_${i}`, 12),
179
+ );
180
+
181
+ render(
182
+ <MemoryRouter>
183
+ <MaterializationsSection nodes={manyNodes} loading={false} />
184
+ </MemoryRouter>,
185
+ );
186
+
187
+ expect(screen.getByText('cube_0')).toBeInTheDocument();
188
+ expect(screen.getByText('cube_4')).toBeInTheDocument();
189
+ expect(screen.queryByText('cube_5')).not.toBeInTheDocument();
190
+ expect(screen.getByText('+5 more')).toBeInTheDocument();
191
+ });
192
+
193
+ it('should handle undefined nodes gracefully', () => {
194
+ render(
195
+ <MemoryRouter>
196
+ <MaterializationsSection nodes={undefined} loading={false} />
197
+ </MemoryRouter>,
198
+ );
199
+
200
+ expect(
201
+ screen.getByText('No materializations configured.'),
202
+ ).toBeInTheDocument();
203
+ });
204
+
205
+ it('should handle nodes without availability', () => {
206
+ const nodeWithoutAvailability = {
207
+ name: 'no_availability_cube',
208
+ type: 'CUBE',
209
+ current: {
210
+ displayName: 'no_availability_cube',
211
+ materializations: [{ name: 'mat1', schedule: '@daily' }],
212
+ },
213
+ };
214
+
215
+ render(
216
+ <MemoryRouter>
217
+ <MaterializationsSection
218
+ nodes={[nodeWithoutAvailability]}
219
+ loading={false}
220
+ />
221
+ </MemoryRouter>,
222
+ );
223
+
224
+ expect(screen.getByText('no_availability_cube')).toBeInTheDocument();
225
+ expect(screen.getByText('⏳ Pending')).toBeInTheDocument();
226
+ });
227
+
228
+ it('should link to nodes with materialization filter', () => {
229
+ render(
230
+ <MemoryRouter>
231
+ <MaterializationsSection nodes={[]} loading={false} />
232
+ </MemoryRouter>,
233
+ );
234
+
235
+ const viewAllLink = screen.getByText('View All →').closest('a');
236
+ expect(viewAllLink).toHaveAttribute('href', '/?hasMaterialization=true');
237
+ });
238
+ });
@@ -0,0 +1,389 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { MyNodesSection } from '../MyNodesSection';
5
+
6
+ jest.mock('../MyWorkspacePage.css', () => ({}));
7
+ jest.mock('../NodeList', () => ({
8
+ NodeList: ({ nodes, showUpdatedAt }) => (
9
+ <div data-testid="node-list">
10
+ {nodes.map(node => (
11
+ <div key={node.name}>{node.name}</div>
12
+ ))}
13
+ </div>
14
+ ),
15
+ }));
16
+ jest.mock('../TypeGroupGrid', () => ({
17
+ TypeGroupGrid: ({ groupedData }) => (
18
+ <div data-testid="type-group-grid">
19
+ {groupedData.map(group => (
20
+ <div key={group.type}>
21
+ {group.type}: {group.count} nodes
22
+ </div>
23
+ ))}
24
+ </div>
25
+ ),
26
+ }));
27
+
28
+ describe('<MyNodesSection />', () => {
29
+ const mockOwnedNodes = [
30
+ {
31
+ name: 'default.owned_metric',
32
+ type: 'METRIC',
33
+ current: { displayName: 'Owned Metric', updatedAt: '2024-01-01' },
34
+ },
35
+ ];
36
+
37
+ const mockWatchedNodes = [
38
+ {
39
+ name: 'default.watched_metric',
40
+ type: 'METRIC',
41
+ current: { displayName: 'Watched Metric', updatedAt: '2024-01-02' },
42
+ },
43
+ {
44
+ name: 'default.owned_metric', // Also owned, should be filtered out
45
+ type: 'METRIC',
46
+ current: { displayName: 'Owned Metric', updatedAt: '2024-01-01' },
47
+ },
48
+ ];
49
+
50
+ const mockRecentlyEdited = [
51
+ {
52
+ name: 'default.edited_metric',
53
+ type: 'METRIC',
54
+ current: { displayName: 'Edited Metric', updatedAt: '2024-01-03' },
55
+ },
56
+ ];
57
+
58
+ it('should render loading state', () => {
59
+ render(
60
+ <MemoryRouter>
61
+ <MyNodesSection
62
+ ownedNodes={[]}
63
+ watchedNodes={[]}
64
+ recentlyEdited={[]}
65
+ username="test.user@example.com"
66
+ loading={true}
67
+ />
68
+ </MemoryRouter>,
69
+ );
70
+
71
+ expect(screen.queryByText('Owned (0)')).not.toBeInTheDocument();
72
+ });
73
+
74
+ it('should render empty state when no nodes', () => {
75
+ render(
76
+ <MemoryRouter>
77
+ <MyNodesSection
78
+ ownedNodes={[]}
79
+ watchedNodes={[]}
80
+ recentlyEdited={[]}
81
+ username="test.user@example.com"
82
+ loading={false}
83
+ />
84
+ </MemoryRouter>,
85
+ );
86
+
87
+ expect(screen.getByText('No nodes yet.')).toBeInTheDocument();
88
+ expect(screen.getByText('Create a node')).toBeInTheDocument();
89
+ expect(screen.getByText('Claim ownership')).toBeInTheDocument();
90
+ });
91
+
92
+ it('should render tabs with node counts', () => {
93
+ render(
94
+ <MemoryRouter>
95
+ <MyNodesSection
96
+ ownedNodes={mockOwnedNodes}
97
+ watchedNodes={mockWatchedNodes}
98
+ recentlyEdited={mockRecentlyEdited}
99
+ username="test.user@example.com"
100
+ loading={false}
101
+ />
102
+ </MemoryRouter>,
103
+ );
104
+
105
+ expect(screen.getByText('Owned (1)')).toBeInTheDocument();
106
+ // Should only count watched nodes that aren't owned (1 watched-only)
107
+ expect(screen.getByText('Watched (1)')).toBeInTheDocument();
108
+ expect(screen.getByText('Recent Edits (1)')).toBeInTheDocument();
109
+ });
110
+
111
+ it('should show owned nodes by default', () => {
112
+ render(
113
+ <MemoryRouter>
114
+ <MyNodesSection
115
+ ownedNodes={mockOwnedNodes}
116
+ watchedNodes={mockWatchedNodes}
117
+ recentlyEdited={mockRecentlyEdited}
118
+ username="test.user@example.com"
119
+ loading={false}
120
+ />
121
+ </MemoryRouter>,
122
+ );
123
+
124
+ expect(screen.getByText('default.owned_metric')).toBeInTheDocument();
125
+ expect(
126
+ screen.queryByText('default.watched_metric'),
127
+ ).not.toBeInTheDocument();
128
+ });
129
+
130
+ it('should switch to watched tab', () => {
131
+ render(
132
+ <MemoryRouter>
133
+ <MyNodesSection
134
+ ownedNodes={mockOwnedNodes}
135
+ watchedNodes={mockWatchedNodes}
136
+ recentlyEdited={mockRecentlyEdited}
137
+ username="test.user@example.com"
138
+ loading={false}
139
+ />
140
+ </MemoryRouter>,
141
+ );
142
+
143
+ fireEvent.click(screen.getByText('Watched (1)'));
144
+
145
+ // Should show watched-only nodes (not owned)
146
+ expect(screen.getByText('default.watched_metric')).toBeInTheDocument();
147
+ expect(screen.queryByText('default.owned_metric')).not.toBeInTheDocument();
148
+ });
149
+
150
+ it('should switch to recent edits tab', () => {
151
+ render(
152
+ <MemoryRouter>
153
+ <MyNodesSection
154
+ ownedNodes={mockOwnedNodes}
155
+ watchedNodes={mockWatchedNodes}
156
+ recentlyEdited={mockRecentlyEdited}
157
+ username="test.user@example.com"
158
+ loading={false}
159
+ />
160
+ </MemoryRouter>,
161
+ );
162
+
163
+ fireEvent.click(screen.getByText('Recent Edits (1)'));
164
+
165
+ expect(screen.getByText('default.edited_metric')).toBeInTheDocument();
166
+ });
167
+
168
+ it('should show empty state for active tab with no nodes', () => {
169
+ render(
170
+ <MemoryRouter>
171
+ <MyNodesSection
172
+ ownedNodes={mockOwnedNodes}
173
+ watchedNodes={[]}
174
+ recentlyEdited={[]}
175
+ username="test.user@example.com"
176
+ loading={false}
177
+ />
178
+ </MemoryRouter>,
179
+ );
180
+
181
+ fireEvent.click(screen.getByText('Watched (0)'));
182
+
183
+ expect(screen.getByText('No watched nodes')).toBeInTheDocument();
184
+ });
185
+
186
+ it('should limit displayed nodes to 8', () => {
187
+ const manyNodes = Array.from({ length: 15 }, (_, i) => ({
188
+ name: `default.metric_${i}`,
189
+ type: 'METRIC',
190
+ current: { displayName: `Metric ${i}`, updatedAt: '2024-01-01' },
191
+ }));
192
+
193
+ render(
194
+ <MemoryRouter>
195
+ <MyNodesSection
196
+ ownedNodes={manyNodes}
197
+ watchedNodes={[]}
198
+ recentlyEdited={[]}
199
+ username="test.user@example.com"
200
+ loading={false}
201
+ />
202
+ </MemoryRouter>,
203
+ );
204
+
205
+ // Should show "+7 more" text
206
+ expect(screen.getByText('+7 more')).toBeInTheDocument();
207
+ });
208
+
209
+ it('should link to browse page with username filter', () => {
210
+ render(
211
+ <MemoryRouter>
212
+ <MyNodesSection
213
+ ownedNodes={mockOwnedNodes}
214
+ watchedNodes={[]}
215
+ recentlyEdited={[]}
216
+ username="test.user@example.com"
217
+ loading={false}
218
+ />
219
+ </MemoryRouter>,
220
+ );
221
+
222
+ // The "View All →" link should have the username filter
223
+ const viewAllLink = screen.getByText('View All →').closest('a');
224
+ expect(viewAllLink).toHaveAttribute(
225
+ 'href',
226
+ '/?ownedBy=test.user@example.com',
227
+ );
228
+ });
229
+
230
+ it('should filter watched nodes to exclude owned nodes', () => {
231
+ const ownedNodes = [
232
+ { name: 'node1', type: 'METRIC', current: {} },
233
+ { name: 'node2', type: 'METRIC', current: {} },
234
+ ];
235
+ const watchedNodes = [
236
+ { name: 'node1', type: 'METRIC', current: {} }, // Also owned
237
+ { name: 'node3', type: 'METRIC', current: {} }, // Watched only
238
+ ];
239
+
240
+ render(
241
+ <MemoryRouter>
242
+ <MyNodesSection
243
+ ownedNodes={ownedNodes}
244
+ watchedNodes={watchedNodes}
245
+ recentlyEdited={[]}
246
+ username="test.user@example.com"
247
+ loading={false}
248
+ />
249
+ </MemoryRouter>,
250
+ );
251
+
252
+ // Watched tab should only show 1 (watched-only nodes)
253
+ expect(screen.getByText('Watched (1)')).toBeInTheDocument();
254
+ });
255
+
256
+ describe('Group by Type feature', () => {
257
+ beforeEach(() => {
258
+ localStorage.clear();
259
+ });
260
+
261
+ it('should show group by type toggle when nodes exist', () => {
262
+ render(
263
+ <MemoryRouter>
264
+ <MyNodesSection
265
+ ownedNodes={mockOwnedNodes}
266
+ watchedNodes={[]}
267
+ recentlyEdited={[]}
268
+ username="test.user@example.com"
269
+ loading={false}
270
+ />
271
+ </MemoryRouter>,
272
+ );
273
+
274
+ expect(screen.getByLabelText('Group by Type')).toBeInTheDocument();
275
+ });
276
+
277
+ it('should not show toggle when no nodes', () => {
278
+ render(
279
+ <MemoryRouter>
280
+ <MyNodesSection
281
+ ownedNodes={[]}
282
+ watchedNodes={[]}
283
+ recentlyEdited={[]}
284
+ username="test.user@example.com"
285
+ loading={false}
286
+ />
287
+ </MemoryRouter>,
288
+ );
289
+
290
+ expect(screen.queryByLabelText('Group by Type')).not.toBeInTheDocument();
291
+ });
292
+
293
+ it('should toggle between list and grouped view', () => {
294
+ render(
295
+ <MemoryRouter>
296
+ <MyNodesSection
297
+ ownedNodes={mockOwnedNodes}
298
+ watchedNodes={[]}
299
+ recentlyEdited={[]}
300
+ username="test.user@example.com"
301
+ loading={false}
302
+ />
303
+ </MemoryRouter>,
304
+ );
305
+
306
+ // Initially should show list view
307
+ expect(screen.getByTestId('node-list')).toBeInTheDocument();
308
+ expect(screen.queryByTestId('type-group-grid')).not.toBeInTheDocument();
309
+
310
+ // Click toggle
311
+ const toggle = screen.getByLabelText('Group by Type');
312
+ fireEvent.click(toggle);
313
+
314
+ // Should now show grouped view
315
+ expect(screen.queryByTestId('node-list')).not.toBeInTheDocument();
316
+ expect(screen.getByTestId('type-group-grid')).toBeInTheDocument();
317
+ });
318
+
319
+ it('should persist toggle state to localStorage', () => {
320
+ render(
321
+ <MemoryRouter>
322
+ <MyNodesSection
323
+ ownedNodes={mockOwnedNodes}
324
+ watchedNodes={[]}
325
+ recentlyEdited={[]}
326
+ username="test.user@example.com"
327
+ loading={false}
328
+ />
329
+ </MemoryRouter>,
330
+ );
331
+
332
+ const toggle = screen.getByLabelText('Group by Type');
333
+ fireEvent.click(toggle);
334
+
335
+ expect(localStorage.getItem('workspace_groupByType')).toBe('true');
336
+
337
+ fireEvent.click(toggle);
338
+ expect(localStorage.getItem('workspace_groupByType')).toBe('false');
339
+ });
340
+
341
+ it('should load toggle state from localStorage', () => {
342
+ localStorage.setItem('workspace_groupByType', 'true');
343
+
344
+ render(
345
+ <MemoryRouter>
346
+ <MyNodesSection
347
+ ownedNodes={mockOwnedNodes}
348
+ watchedNodes={[]}
349
+ recentlyEdited={[]}
350
+ username="test.user@example.com"
351
+ loading={false}
352
+ />
353
+ </MemoryRouter>,
354
+ );
355
+
356
+ // Should show grouped view on mount
357
+ expect(screen.getByTestId('type-group-grid')).toBeInTheDocument();
358
+ expect(screen.getByLabelText('Group by Type')).toBeChecked();
359
+ });
360
+
361
+ it('should group nodes by type correctly', () => {
362
+ const mixedNodes = [
363
+ { name: 'metric1', type: 'metric', current: {} },
364
+ { name: 'metric2', type: 'metric', current: {} },
365
+ { name: 'dim1', type: 'dimension', current: {} },
366
+ { name: 'source1', type: 'source', current: {} },
367
+ ];
368
+
369
+ render(
370
+ <MemoryRouter>
371
+ <MyNodesSection
372
+ ownedNodes={mixedNodes}
373
+ watchedNodes={[]}
374
+ recentlyEdited={[]}
375
+ username="test.user@example.com"
376
+ loading={false}
377
+ />
378
+ </MemoryRouter>,
379
+ );
380
+
381
+ const toggle = screen.getByLabelText('Group by Type');
382
+ fireEvent.click(toggle);
383
+
384
+ expect(screen.getByText('metric: 2 nodes')).toBeInTheDocument();
385
+ expect(screen.getByText('dimension: 1 nodes')).toBeInTheDocument();
386
+ expect(screen.getByText('source: 1 nodes')).toBeInTheDocument();
387
+ });
388
+ });
389
+ });