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,347 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { MyWorkspacePage } from '../index';
5
+ import * as useWorkspaceData from '../../../hooks/useWorkspaceData';
6
+
7
+ // Mock CSS imports
8
+ jest.mock('../MyWorkspacePage.css', () => ({}));
9
+ jest.mock('../../../../styles/settings.css', () => ({}));
10
+
11
+ // Mock child components to test integration
12
+ jest.mock('../NotificationsSection', () => ({
13
+ NotificationsSection: ({ notifications, loading }) => (
14
+ <div data-testid="notifications-section">
15
+ {loading ? 'Loading...' : `${notifications.length} notifications`}
16
+ </div>
17
+ ),
18
+ }));
19
+
20
+ jest.mock('../NeedsAttentionSection', () => ({
21
+ NeedsAttentionSection: ({ loading }) => (
22
+ <div data-testid="needs-attention-section">
23
+ {loading ? 'Loading...' : 'Needs Attention'}
24
+ </div>
25
+ ),
26
+ }));
27
+
28
+ jest.mock('../MyNodesSection', () => ({
29
+ MyNodesSection: ({ ownedNodes, loading }) => (
30
+ <div data-testid="my-nodes-section">
31
+ {loading ? 'Loading...' : `${ownedNodes.length} owned nodes`}
32
+ </div>
33
+ ),
34
+ }));
35
+
36
+ jest.mock('../CollectionsSection', () => ({
37
+ CollectionsSection: ({ collections, loading }) => (
38
+ <div data-testid="collections-section">
39
+ {loading ? 'Loading...' : `${collections.length} collections`}
40
+ </div>
41
+ ),
42
+ }));
43
+
44
+ jest.mock('../MaterializationsSection', () => ({
45
+ MaterializationsSection: ({ nodes, loading }) => (
46
+ <div data-testid="materializations-section">
47
+ {loading ? 'Loading...' : `${nodes.length} materializations`}
48
+ </div>
49
+ ),
50
+ }));
51
+
52
+ jest.mock('../ActiveBranchesSection', () => ({
53
+ ActiveBranchesSection: ({ loading }) => (
54
+ <div data-testid="active-branches-section">
55
+ {loading ? 'Loading...' : 'Active Branches'}
56
+ </div>
57
+ ),
58
+ }));
59
+
60
+ describe('<MyWorkspacePage />', () => {
61
+ const mockCurrentUser = {
62
+ username: 'test.user@example.com',
63
+ email: 'test.user@example.com',
64
+ };
65
+
66
+ const mockOwnedNodes = [
67
+ {
68
+ name: 'default.test_metric',
69
+ type: 'METRIC',
70
+ current: { displayName: 'Test Metric', updatedAt: '2024-01-01' },
71
+ },
72
+ {
73
+ name: 'default.test_dimension',
74
+ type: 'DIMENSION',
75
+ current: { displayName: 'Test Dimension', updatedAt: '2024-01-02' },
76
+ },
77
+ ];
78
+
79
+ const mockCollections = [
80
+ {
81
+ name: 'test_collection',
82
+ description: 'Test Collection',
83
+ nodeCount: 5,
84
+ createdBy: 'test.user@example.com',
85
+ },
86
+ ];
87
+
88
+ const mockNotifications = [
89
+ {
90
+ entity_name: 'default.test_metric',
91
+ entity_type: 'node',
92
+ activity_type: 'update',
93
+ created_at: '2024-01-01T00:00:00Z',
94
+ user: 'test.user@example.com',
95
+ node_type: 'metric',
96
+ display_name: 'Test Metric',
97
+ },
98
+ ];
99
+
100
+ const mockMaterializations = [
101
+ {
102
+ name: 'default.test_cube',
103
+ type: 'CUBE',
104
+ current: {
105
+ displayName: 'Test Cube',
106
+ availability: {
107
+ validThroughTs: Date.now() - 1000 * 60 * 60, // 1 hour ago
108
+ },
109
+ materializations: [{ name: 'mat1', schedule: '@daily' }],
110
+ },
111
+ },
112
+ ];
113
+
114
+ const mockNeedsAttention = {
115
+ nodesMissingDescription: [],
116
+ invalidNodes: [],
117
+ staleDrafts: [],
118
+ orphanedDimensions: [],
119
+ };
120
+
121
+ beforeEach(() => {
122
+ jest.clearAllMocks();
123
+ });
124
+
125
+ it('should render loading state', () => {
126
+ jest.spyOn(useWorkspaceData, 'useCurrentUser').mockReturnValue({
127
+ data: null,
128
+ loading: true,
129
+ error: null,
130
+ });
131
+
132
+ render(
133
+ <MemoryRouter>
134
+ <MyWorkspacePage />
135
+ </MemoryRouter>,
136
+ );
137
+
138
+ expect(screen.getByText('Dashboard')).toBeInTheDocument();
139
+ // Should show loading icon when user is loading
140
+ expect(screen.queryByTestId('collections-section')).not.toBeInTheDocument();
141
+ });
142
+
143
+ it('should render all sections when data is loaded', () => {
144
+ // Mock all hooks with data
145
+ jest.spyOn(useWorkspaceData, 'useCurrentUser').mockReturnValue({
146
+ data: mockCurrentUser,
147
+ loading: false,
148
+ error: null,
149
+ });
150
+ jest.spyOn(useWorkspaceData, 'useWorkspaceOwnedNodes').mockReturnValue({
151
+ data: mockOwnedNodes,
152
+ loading: false,
153
+ error: null,
154
+ });
155
+ jest.spyOn(useWorkspaceData, 'useWorkspaceRecentlyEdited').mockReturnValue({
156
+ data: [],
157
+ loading: false,
158
+ error: null,
159
+ });
160
+ jest.spyOn(useWorkspaceData, 'useWorkspaceWatchedNodes').mockReturnValue({
161
+ data: [],
162
+ loading: false,
163
+ error: null,
164
+ });
165
+ jest.spyOn(useWorkspaceData, 'useWorkspaceCollections').mockReturnValue({
166
+ data: mockCollections,
167
+ loading: false,
168
+ error: null,
169
+ });
170
+ jest.spyOn(useWorkspaceData, 'useWorkspaceNotifications').mockReturnValue({
171
+ data: mockNotifications,
172
+ loading: false,
173
+ error: null,
174
+ });
175
+ jest
176
+ .spyOn(useWorkspaceData, 'useWorkspaceMaterializations')
177
+ .mockReturnValue({
178
+ data: mockMaterializations,
179
+ loading: false,
180
+ error: null,
181
+ });
182
+ jest.spyOn(useWorkspaceData, 'useWorkspaceNeedsAttention').mockReturnValue({
183
+ data: mockNeedsAttention,
184
+ loading: false,
185
+ error: null,
186
+ });
187
+ jest.spyOn(useWorkspaceData, 'usePersonalNamespace').mockReturnValue({
188
+ exists: true,
189
+ loading: false,
190
+ error: null,
191
+ });
192
+
193
+ render(
194
+ <MemoryRouter>
195
+ <MyWorkspacePage />
196
+ </MemoryRouter>,
197
+ );
198
+
199
+ // Check all sections are rendered
200
+ expect(screen.getByTestId('collections-section')).toHaveTextContent(
201
+ '1 collections',
202
+ );
203
+ expect(screen.getByTestId('my-nodes-section')).toHaveTextContent(
204
+ '2 owned nodes',
205
+ );
206
+ expect(screen.getByTestId('notifications-section')).toHaveTextContent(
207
+ '1 notifications',
208
+ );
209
+ expect(screen.getByTestId('materializations-section')).toHaveTextContent(
210
+ '1 materializations',
211
+ );
212
+ expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument();
213
+ expect(screen.getByTestId('active-branches-section')).toBeInTheDocument();
214
+ });
215
+
216
+ it('should pass correct props to sections', () => {
217
+ jest.spyOn(useWorkspaceData, 'useCurrentUser').mockReturnValue({
218
+ data: mockCurrentUser,
219
+ loading: false,
220
+ error: null,
221
+ });
222
+ jest.spyOn(useWorkspaceData, 'useWorkspaceOwnedNodes').mockReturnValue({
223
+ data: mockOwnedNodes,
224
+ loading: false,
225
+ error: null,
226
+ });
227
+ jest.spyOn(useWorkspaceData, 'useWorkspaceRecentlyEdited').mockReturnValue({
228
+ data: [],
229
+ loading: false,
230
+ error: null,
231
+ });
232
+ jest.spyOn(useWorkspaceData, 'useWorkspaceWatchedNodes').mockReturnValue({
233
+ data: [],
234
+ loading: false,
235
+ error: null,
236
+ });
237
+ jest.spyOn(useWorkspaceData, 'useWorkspaceCollections').mockReturnValue({
238
+ data: mockCollections,
239
+ loading: false,
240
+ error: null,
241
+ });
242
+ jest.spyOn(useWorkspaceData, 'useWorkspaceNotifications').mockReturnValue({
243
+ data: mockNotifications,
244
+ loading: false,
245
+ error: null,
246
+ });
247
+ jest
248
+ .spyOn(useWorkspaceData, 'useWorkspaceMaterializations')
249
+ .mockReturnValue({
250
+ data: mockMaterializations,
251
+ loading: false,
252
+ error: null,
253
+ });
254
+ jest.spyOn(useWorkspaceData, 'useWorkspaceNeedsAttention').mockReturnValue({
255
+ data: mockNeedsAttention,
256
+ loading: false,
257
+ error: null,
258
+ });
259
+ jest.spyOn(useWorkspaceData, 'usePersonalNamespace').mockReturnValue({
260
+ exists: true,
261
+ loading: false,
262
+ error: null,
263
+ });
264
+
265
+ render(
266
+ <MemoryRouter>
267
+ <MyWorkspacePage />
268
+ </MemoryRouter>,
269
+ );
270
+
271
+ // Verify sections receive correct data
272
+ expect(screen.getByTestId('collections-section')).toBeInTheDocument();
273
+ expect(screen.getByTestId('my-nodes-section')).toBeInTheDocument();
274
+ expect(screen.getByTestId('notifications-section')).toBeInTheDocument();
275
+ });
276
+
277
+ it('should calculate stale materializations correctly', () => {
278
+ const now = Date.now();
279
+ const staleNode = {
280
+ name: 'default.stale_cube',
281
+ type: 'CUBE',
282
+ current: {
283
+ displayName: 'Stale Cube',
284
+ availability: {
285
+ validThroughTs: now - 1000 * 60 * 60 * 80, // 80 hours ago (> 72 hours)
286
+ },
287
+ },
288
+ };
289
+
290
+ jest.spyOn(useWorkspaceData, 'useCurrentUser').mockReturnValue({
291
+ data: mockCurrentUser,
292
+ loading: false,
293
+ error: null,
294
+ });
295
+ jest.spyOn(useWorkspaceData, 'useWorkspaceOwnedNodes').mockReturnValue({
296
+ data: [],
297
+ loading: false,
298
+ error: null,
299
+ });
300
+ jest.spyOn(useWorkspaceData, 'useWorkspaceRecentlyEdited').mockReturnValue({
301
+ data: [],
302
+ loading: false,
303
+ error: null,
304
+ });
305
+ jest.spyOn(useWorkspaceData, 'useWorkspaceWatchedNodes').mockReturnValue({
306
+ data: [],
307
+ loading: false,
308
+ error: null,
309
+ });
310
+ jest.spyOn(useWorkspaceData, 'useWorkspaceCollections').mockReturnValue({
311
+ data: [],
312
+ loading: false,
313
+ error: null,
314
+ });
315
+ jest.spyOn(useWorkspaceData, 'useWorkspaceNotifications').mockReturnValue({
316
+ data: [],
317
+ loading: false,
318
+ error: null,
319
+ });
320
+ jest
321
+ .spyOn(useWorkspaceData, 'useWorkspaceMaterializations')
322
+ .mockReturnValue({
323
+ data: [staleNode],
324
+ loading: false,
325
+ error: null,
326
+ });
327
+ jest.spyOn(useWorkspaceData, 'useWorkspaceNeedsAttention').mockReturnValue({
328
+ data: mockNeedsAttention,
329
+ loading: false,
330
+ error: null,
331
+ });
332
+ jest.spyOn(useWorkspaceData, 'usePersonalNamespace').mockReturnValue({
333
+ exists: true,
334
+ loading: false,
335
+ error: null,
336
+ });
337
+
338
+ render(
339
+ <MemoryRouter>
340
+ <MyWorkspacePage />
341
+ </MemoryRouter>,
342
+ );
343
+
344
+ // The stale materialization should be passed to NeedsAttentionSection
345
+ expect(screen.getByTestId('needs-attention-section')).toBeInTheDocument();
346
+ });
347
+ });
@@ -0,0 +1,272 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import { NeedsAttentionSection } from '../NeedsAttentionSection';
5
+
6
+ jest.mock('../MyWorkspacePage.css', () => ({}));
7
+
8
+ describe('<NeedsAttentionSection />', () => {
9
+ const defaultProps = {
10
+ nodesMissingDescription: [],
11
+ invalidNodes: [],
12
+ staleDrafts: [],
13
+ staleMaterializations: [],
14
+ orphanedDimensions: [],
15
+ username: 'test.user@example.com',
16
+ hasItems: false,
17
+ loading: false,
18
+ personalNamespace: 'users.test.user',
19
+ hasPersonalNamespace: true,
20
+ namespaceLoading: false,
21
+ };
22
+
23
+ it('should render loading state', () => {
24
+ render(
25
+ <MemoryRouter>
26
+ <NeedsAttentionSection {...defaultProps} loading={true} />
27
+ </MemoryRouter>,
28
+ );
29
+
30
+ expect(screen.queryByText('✓ All good!')).not.toBeInTheDocument();
31
+ });
32
+
33
+ it('should render all categories', () => {
34
+ render(
35
+ <MemoryRouter>
36
+ <NeedsAttentionSection {...defaultProps} />
37
+ </MemoryRouter>,
38
+ );
39
+
40
+ expect(screen.getByText(/Invalid/)).toBeInTheDocument();
41
+ expect(screen.getByText(/Stale Drafts/)).toBeInTheDocument();
42
+ expect(screen.getByText(/Stale Materializations/)).toBeInTheDocument();
43
+ expect(screen.getByText(/No Description/)).toBeInTheDocument();
44
+ expect(screen.getByText(/Orphaned Dimensions/)).toBeInTheDocument();
45
+ });
46
+
47
+ it('should show "All good!" when category has no items', () => {
48
+ render(
49
+ <MemoryRouter>
50
+ <NeedsAttentionSection {...defaultProps} />
51
+ </MemoryRouter>,
52
+ );
53
+
54
+ const allGood = screen.getAllByText('✓ All good!');
55
+ expect(allGood.length).toBe(5); // All 5 categories should show "All good!"
56
+ });
57
+
58
+ it('should display invalid nodes', () => {
59
+ const invalidNodes = [
60
+ {
61
+ name: 'default.invalid_metric',
62
+ type: 'METRIC',
63
+ current: { displayName: 'Invalid Metric' },
64
+ },
65
+ ];
66
+
67
+ render(
68
+ <MemoryRouter>
69
+ <NeedsAttentionSection
70
+ {...defaultProps}
71
+ invalidNodes={invalidNodes}
72
+ hasItems={true}
73
+ />
74
+ </MemoryRouter>,
75
+ );
76
+
77
+ expect(screen.getByText('❌ Invalid')).toBeInTheDocument();
78
+ expect(screen.getByText('(1)')).toBeInTheDocument();
79
+ });
80
+
81
+ it('should display stale drafts', () => {
82
+ const staleDrafts = [
83
+ {
84
+ name: 'default.stale_draft',
85
+ type: 'METRIC',
86
+ current: { displayName: 'Stale Draft' },
87
+ },
88
+ ];
89
+
90
+ render(
91
+ <MemoryRouter>
92
+ <NeedsAttentionSection
93
+ {...defaultProps}
94
+ staleDrafts={staleDrafts}
95
+ hasItems={true}
96
+ />
97
+ </MemoryRouter>,
98
+ );
99
+
100
+ expect(screen.getByText('⏰ Stale Drafts')).toBeInTheDocument();
101
+ expect(screen.getByText('(1)')).toBeInTheDocument();
102
+ });
103
+
104
+ it('should display nodes missing description', () => {
105
+ const nodesMissingDescription = [
106
+ {
107
+ name: 'default.no_desc_metric',
108
+ type: 'METRIC',
109
+ current: { displayName: 'No Description Metric' },
110
+ },
111
+ ];
112
+
113
+ render(
114
+ <MemoryRouter>
115
+ <NeedsAttentionSection
116
+ {...defaultProps}
117
+ nodesMissingDescription={nodesMissingDescription}
118
+ hasItems={true}
119
+ />
120
+ </MemoryRouter>,
121
+ );
122
+
123
+ expect(screen.getByText('📝 No Description')).toBeInTheDocument();
124
+ expect(screen.getByText('(1)')).toBeInTheDocument();
125
+ });
126
+
127
+ it('should show "View all" link for categories with items', () => {
128
+ const invalidNodes = [
129
+ {
130
+ name: 'default.invalid_metric',
131
+ type: 'METRIC',
132
+ current: { displayName: 'Invalid Metric' },
133
+ },
134
+ ];
135
+
136
+ render(
137
+ <MemoryRouter>
138
+ <NeedsAttentionSection
139
+ {...defaultProps}
140
+ invalidNodes={invalidNodes}
141
+ hasItems={true}
142
+ />
143
+ </MemoryRouter>,
144
+ );
145
+
146
+ const viewAllLinks = screen.getAllByText('View all →');
147
+ expect(viewAllLinks.length).toBeGreaterThan(0);
148
+ });
149
+
150
+ it('should not show "View all" link for empty categories', () => {
151
+ render(
152
+ <MemoryRouter>
153
+ <NeedsAttentionSection {...defaultProps} />
154
+ </MemoryRouter>,
155
+ );
156
+
157
+ expect(screen.queryByText('View all →')).not.toBeInTheDocument();
158
+ });
159
+
160
+ it('should limit nodes to 10 per category', () => {
161
+ const manyNodes = Array.from({ length: 15 }, (_, i) => ({
162
+ name: `default.node_${i}`,
163
+ type: 'METRIC',
164
+ current: { displayName: `Node ${i}` },
165
+ }));
166
+
167
+ render(
168
+ <MemoryRouter>
169
+ <NeedsAttentionSection
170
+ {...defaultProps}
171
+ invalidNodes={manyNodes}
172
+ hasItems={true}
173
+ />
174
+ </MemoryRouter>,
175
+ );
176
+
177
+ // Should only display 10 nodes (via NodeChip component)
178
+ // The 11th node should not be displayed
179
+ expect(screen.getByText('❌ Invalid')).toBeInTheDocument();
180
+ expect(screen.getByText('(15)')).toBeInTheDocument();
181
+ });
182
+
183
+ it('should show personal namespace prompt when namespace does not exist', () => {
184
+ render(
185
+ <MemoryRouter>
186
+ <NeedsAttentionSection {...defaultProps} hasPersonalNamespace={false} />
187
+ </MemoryRouter>,
188
+ );
189
+
190
+ expect(screen.getByText('Set up your namespace')).toBeInTheDocument();
191
+ expect(screen.getByText('users.test.user')).toBeInTheDocument();
192
+ expect(screen.getByText('Create →')).toBeInTheDocument();
193
+ });
194
+
195
+ it('should not show personal namespace prompt when namespace exists', () => {
196
+ render(
197
+ <MemoryRouter>
198
+ <NeedsAttentionSection {...defaultProps} hasPersonalNamespace={true} />
199
+ </MemoryRouter>,
200
+ );
201
+
202
+ expect(screen.queryByText('Set up your namespace')).not.toBeInTheDocument();
203
+ });
204
+
205
+ it('should not show personal namespace prompt when loading', () => {
206
+ render(
207
+ <MemoryRouter>
208
+ <NeedsAttentionSection
209
+ {...defaultProps}
210
+ hasPersonalNamespace={false}
211
+ namespaceLoading={true}
212
+ />
213
+ </MemoryRouter>,
214
+ );
215
+
216
+ expect(screen.queryByText('Set up your namespace')).not.toBeInTheDocument();
217
+ });
218
+
219
+ it('should link to correct filter URLs', () => {
220
+ const invalidNodes = [
221
+ {
222
+ name: 'default.invalid_metric',
223
+ type: 'METRIC',
224
+ current: { displayName: 'Invalid Metric' },
225
+ },
226
+ ];
227
+
228
+ render(
229
+ <MemoryRouter>
230
+ <NeedsAttentionSection
231
+ {...defaultProps}
232
+ invalidNodes={invalidNodes}
233
+ hasItems={true}
234
+ />
235
+ </MemoryRouter>,
236
+ );
237
+
238
+ const viewAllLink = screen.getByText('View all →').closest('a');
239
+ expect(viewAllLink).toHaveAttribute(
240
+ 'href',
241
+ '/?ownedBy=test.user@example.com&statuses=INVALID',
242
+ );
243
+ });
244
+
245
+ it('should display correct count for each category', () => {
246
+ render(
247
+ <MemoryRouter>
248
+ <NeedsAttentionSection
249
+ {...defaultProps}
250
+ invalidNodes={[{ name: 'node1', type: 'METRIC', current: {} }]}
251
+ staleDrafts={[
252
+ { name: 'node2', type: 'METRIC', current: {} },
253
+ { name: 'node3', type: 'METRIC', current: {} },
254
+ ]}
255
+ nodesMissingDescription={[
256
+ { name: 'node4', type: 'METRIC', current: {} },
257
+ { name: 'node5', type: 'METRIC', current: {} },
258
+ { name: 'node6', type: 'METRIC', current: {} },
259
+ ]}
260
+ hasItems={true}
261
+ />
262
+ </MemoryRouter>,
263
+ );
264
+
265
+ expect(screen.getByText('❌ Invalid')).toBeInTheDocument();
266
+ expect(screen.getByText('(1)')).toBeInTheDocument();
267
+ expect(screen.getByText('⏰ Stale Drafts')).toBeInTheDocument();
268
+ expect(screen.getByText('(2)')).toBeInTheDocument();
269
+ expect(screen.getByText('📝 No Description')).toBeInTheDocument();
270
+ expect(screen.getByText('(3)')).toBeInTheDocument();
271
+ });
272
+ });