@cccsaurora/howler-ui 2.15.0-dev.333 → 2.15.0-dev.335

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.
@@ -2,9 +2,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Avatar, Tooltip, useTheme } from '@mui/material';
3
3
  import { AvatarContext } from '@cccsaurora/howler-ui/components/app/providers/AvatarProvider';
4
4
  import { memo, useCallback, useContext, useEffect, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
5
6
  import { nameToInitials } from '@cccsaurora/howler-ui/utils/stringUtils';
6
7
  import { stringToColor } from '@cccsaurora/howler-ui/utils/utils';
7
8
  const HowlerAvatar = ({ userId, ...avatarProps }) => {
9
+ const { t } = useTranslation();
8
10
  const { getAvatar } = useContext(AvatarContext);
9
11
  const theme = useTheme();
10
12
  const [props, setProps] = useState();
@@ -35,10 +37,10 @@ const HowlerAvatar = ({ userId, ...avatarProps }) => {
35
37
  // eslint-disable-next-line react-hooks/exhaustive-deps
36
38
  }, [userId]);
37
39
  if (userId) {
38
- return (_jsx(Tooltip, { title: userId, children: _jsx(Avatar, { ...avatarProps, ...props, sx: { ...(avatarProps?.sx || {}), ...(props?.sx || {}) } }) }));
40
+ return (_jsx(Tooltip, { title: userId, children: _jsx(Avatar, { "aria-label": userId, ...avatarProps, ...props, sx: { ...(avatarProps?.sx || {}), ...(props?.sx || {}) } }) }));
39
41
  }
40
42
  else {
41
- return (_jsx(Avatar, { ...avatarProps, ...props, sx: { ...(avatarProps?.sx || {}), ...(props?.sx || {}) } }));
43
+ return (_jsx(Avatar, { "aria-label": t('unknown'), ...avatarProps, ...props, sx: { ...(avatarProps?.sx || {}), ...(props?.sx || {}) } }));
42
44
  }
43
45
  };
44
46
  export default memo(HowlerAvatar);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,410 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /* eslint-disable react/jsx-no-literals */
3
+ /* eslint-disable import/imports-first */
4
+ /// <reference types="vitest" />
5
+ import { render, screen, waitFor } from '@testing-library/react';
6
+ import userEvent, {} from '@testing-library/user-event';
7
+ import { AvatarContext } from '@cccsaurora/howler-ui/components/app/providers/AvatarProvider';
8
+ import i18n from '@cccsaurora/howler-ui/i18n';
9
+ import { act } from 'react';
10
+ import { I18nextProvider } from 'react-i18next';
11
+ import { createMockDossier } from '@cccsaurora/howler-ui/tests/utils';
12
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
13
+ import DossierCard from './DossierCard';
14
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
15
+ const mockAvatarContext = {
16
+ getAvatar: vi.fn(userId => Promise.resolve('https://images.example.com/' + userId))
17
+ };
18
+ // Test wrapper
19
+ const Wrapper = ({ children }) => {
20
+ return (_jsx(I18nextProvider, { i18n: i18n, children: _jsx(AvatarContext.Provider, { value: mockAvatarContext, children: children }) }));
21
+ };
22
+ describe('DossierCard', () => {
23
+ let user;
24
+ let mockOnDelete;
25
+ beforeEach(() => {
26
+ user = userEvent.setup();
27
+ mockOnDelete = vi.fn();
28
+ vi.clearAllMocks();
29
+ });
30
+ describe('Rendering Conditions', () => {
31
+ it('should render with required props only', async () => {
32
+ const dossier = createMockDossier();
33
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
34
+ await waitFor(() => {
35
+ expect(screen.getByText('Test Dossier')).toBeInTheDocument();
36
+ });
37
+ });
38
+ it('should render with all props', async () => {
39
+ const dossier = createMockDossier();
40
+ render(_jsx(DossierCard, { dossier: dossier, className: "custom-class", onDelete: mockOnDelete }), {
41
+ wrapper: Wrapper
42
+ });
43
+ await waitFor(() => {
44
+ expect(screen.getByText('Test Dossier')).toBeInTheDocument();
45
+ });
46
+ });
47
+ it('should apply custom className when provided', async () => {
48
+ const dossier = createMockDossier();
49
+ const { container } = render(_jsx(DossierCard, { dossier: dossier, className: "custom-class" }), { wrapper: Wrapper });
50
+ await waitFor(() => {
51
+ const card = container.querySelector('.custom-class');
52
+ expect(card).toBeInTheDocument();
53
+ });
54
+ });
55
+ it('should render without className when not provided', async () => {
56
+ const dossier = createMockDossier();
57
+ const { container } = render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
58
+ await waitFor(() => {
59
+ expect(container.querySelector('.MuiCard-root')).toBeInTheDocument();
60
+ });
61
+ });
62
+ });
63
+ describe('UI Element Display', () => {
64
+ it('should display dossier title', async () => {
65
+ const dossier = createMockDossier({ title: 'My Custom Dossier' });
66
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
67
+ await waitFor(() => {
68
+ expect(screen.getByText('My Custom Dossier')).toBeInTheDocument();
69
+ });
70
+ });
71
+ it('should display dossier query in code block', async () => {
72
+ const dossier = createMockDossier({ query: 'howler.id:*' });
73
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
74
+ await waitFor(() => {
75
+ const codeElement = screen.getByText('howler.id:*');
76
+ expect(codeElement).toBeInTheDocument();
77
+ expect(codeElement.tagName).toBe('CODE');
78
+ });
79
+ });
80
+ it('should display Person icon for personal dossier type', async () => {
81
+ const dossier = createMockDossier({ type: 'personal' });
82
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
83
+ await waitFor(() => {
84
+ // Check for Person icon via MUI's data-testid or by checking the tooltip
85
+ const tooltip = screen.getByLabelText(/personal/i);
86
+ expect(tooltip).toBeInTheDocument();
87
+ });
88
+ });
89
+ it('should display Language icon for global dossier type', async () => {
90
+ const dossier = createMockDossier({ type: 'global' });
91
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
92
+ await waitFor(() => {
93
+ // Check for Language icon via tooltip
94
+ const tooltip = screen.getByLabelText(/global/i);
95
+ expect(tooltip).toBeInTheDocument();
96
+ });
97
+ });
98
+ it('should display HowlerAvatar with correct userId', async () => {
99
+ const dossier = createMockDossier({ owner: 'john.doe' });
100
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
101
+ await waitFor(() => {
102
+ expect(screen.getByLabelText('john.doe')).toBeInTheDocument();
103
+ });
104
+ });
105
+ it('should display delete button when onDelete is provided', async () => {
106
+ const dossier = createMockDossier();
107
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
108
+ await waitFor(() => {
109
+ const deleteButton = screen.getByRole('button');
110
+ expect(deleteButton).toBeInTheDocument();
111
+ });
112
+ });
113
+ it('should not display delete button when onDelete is not provided', async () => {
114
+ const dossier = createMockDossier();
115
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
116
+ await waitFor(() => {
117
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
118
+ });
119
+ });
120
+ it('should have tooltip on type icon', async () => {
121
+ const dossier = createMockDossier({ type: 'personal' });
122
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
123
+ await waitFor(() => {
124
+ expect(screen.getByLabelText(/personal/i)).toBeInTheDocument();
125
+ });
126
+ });
127
+ it('should have tooltip on delete button', async () => {
128
+ const dossier = createMockDossier();
129
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
130
+ await waitFor(() => {
131
+ expect(screen.getByLabelText(/delete/i)).toBeInTheDocument();
132
+ });
133
+ });
134
+ });
135
+ describe('Button States & Interactions', () => {
136
+ it('should call onDelete with correct parameters when delete button is clicked', async () => {
137
+ const dossier = createMockDossier({ dossier_id: 'my-dossier-123' });
138
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
139
+ await waitFor(() => {
140
+ expect(screen.getByRole('button')).toBeInTheDocument();
141
+ });
142
+ const deleteButton = screen.getByRole('button');
143
+ await user.click(deleteButton);
144
+ await waitFor(() => {
145
+ expect(mockOnDelete).toHaveBeenCalledTimes(1);
146
+ expect(mockOnDelete).toHaveBeenCalledWith(expect.any(Object), 'my-dossier-123');
147
+ });
148
+ });
149
+ it('should call onDelete with event object', async () => {
150
+ const dossier = createMockDossier();
151
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
152
+ await waitFor(() => {
153
+ expect(screen.getByRole('button')).toBeInTheDocument();
154
+ });
155
+ const deleteButton = screen.getByRole('button');
156
+ await user.click(deleteButton);
157
+ await waitFor(() => {
158
+ const eventArg = mockOnDelete.mock.calls[0][0];
159
+ expect(eventArg).toBeDefined();
160
+ expect(eventArg.type).toBe('click');
161
+ });
162
+ });
163
+ it('should not crash when delete button is clicked multiple times', async () => {
164
+ const dossier = createMockDossier();
165
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
166
+ await waitFor(() => {
167
+ expect(screen.getByRole('button')).toBeInTheDocument();
168
+ });
169
+ const deleteButton = screen.getByRole('button');
170
+ await user.click(deleteButton);
171
+ await user.click(deleteButton);
172
+ await user.click(deleteButton);
173
+ await waitFor(() => {
174
+ expect(mockOnDelete).toHaveBeenCalledTimes(3);
175
+ });
176
+ });
177
+ });
178
+ describe('Edge Cases', () => {
179
+ it('should handle empty title', async () => {
180
+ const dossier = createMockDossier({ title: '' });
181
+ const { container } = render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
182
+ await waitFor(() => {
183
+ expect(container).toBeInTheDocument();
184
+ });
185
+ });
186
+ it('should handle empty query', async () => {
187
+ const dossier = createMockDossier({ query: '' });
188
+ const { container } = render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
189
+ await waitFor(() => {
190
+ const codeElement = container.querySelector('code');
191
+ expect(codeElement).toBeEmptyDOMElement();
192
+ });
193
+ });
194
+ it('should handle very long title', async () => {
195
+ const longTitle = 'A'.repeat(200);
196
+ const dossier = createMockDossier({ title: longTitle });
197
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
198
+ await waitFor(() => {
199
+ expect(screen.getByText(longTitle)).toBeInTheDocument();
200
+ });
201
+ });
202
+ it('should handle very long query', async () => {
203
+ const longQuery = 'howler.id:*' + ' AND howler.status:open'.repeat(50);
204
+ const dossier = createMockDossier({ query: longQuery });
205
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
206
+ await waitFor(() => {
207
+ expect(screen.getByText(longQuery)).toBeInTheDocument();
208
+ });
209
+ });
210
+ it('should handle special characters in title', async () => {
211
+ const dossier = createMockDossier({ title: '<script>alert("xss")</script>' });
212
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
213
+ await waitFor(() => {
214
+ expect(screen.getByText('<script>alert("xss")</script>')).toBeInTheDocument();
215
+ });
216
+ });
217
+ it('should handle special characters in query', async () => {
218
+ const dossier = createMockDossier({ query: 'howler.id:*&query=<test>' });
219
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
220
+ await waitFor(() => {
221
+ expect(screen.getByText('howler.id:*&query=<test>')).toBeInTheDocument();
222
+ });
223
+ });
224
+ it('should handle undefined owner', async () => {
225
+ const dossier = createMockDossier({ owner: undefined });
226
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
227
+ await waitFor(() => {
228
+ expect(screen.getByLabelText('Unknown')).toBeInTheDocument();
229
+ });
230
+ });
231
+ it('should handle null owner', async () => {
232
+ const dossier = createMockDossier({ owner: null });
233
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
234
+ await waitFor(() => {
235
+ expect(screen.getByLabelText('Unknown')).toBeInTheDocument();
236
+ });
237
+ });
238
+ });
239
+ describe('Dossier Types', () => {
240
+ it('should render correctly for personal type', async () => {
241
+ const dossier = createMockDossier({ type: 'personal' });
242
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
243
+ await waitFor(() => {
244
+ expect(screen.getByLabelText(/personal/i)).toBeInTheDocument();
245
+ });
246
+ });
247
+ it('should render correctly for global type', async () => {
248
+ const dossier = createMockDossier({ type: 'global' });
249
+ render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
250
+ await waitFor(() => {
251
+ expect(screen.getByLabelText(/global/i)).toBeInTheDocument();
252
+ });
253
+ });
254
+ it('should handle switching between types', async () => {
255
+ const { rerender } = render(_jsx(DossierCard, { dossier: createMockDossier({ type: 'personal' }) }), {
256
+ wrapper: Wrapper
257
+ });
258
+ await waitFor(() => {
259
+ expect(screen.getByLabelText(/personal/i)).toBeInTheDocument();
260
+ });
261
+ rerender(_jsx(DossierCard, { dossier: createMockDossier({ type: 'global' }) }));
262
+ await waitFor(() => {
263
+ expect(screen.getByLabelText(/global/i)).toBeInTheDocument();
264
+ });
265
+ });
266
+ });
267
+ describe('Integration Tests', () => {
268
+ it('should render complete card with all elements', async () => {
269
+ const dossier = createMockDossier({
270
+ dossier_id: 'full-test',
271
+ title: 'Complete Dossier',
272
+ query: 'howler.status:open AND howler.assigned:me',
273
+ type: 'personal',
274
+ owner: 'admin'
275
+ });
276
+ render(_jsx(DossierCard, { dossier: dossier, className: "test-class", onDelete: mockOnDelete }), {
277
+ wrapper: Wrapper
278
+ });
279
+ await waitFor(() => {
280
+ // Check all elements are present
281
+ expect(screen.getByText('Complete Dossier')).toBeInTheDocument();
282
+ expect(screen.getByText('howler.status:open AND howler.assigned:me')).toBeInTheDocument();
283
+ expect(screen.getByLabelText(/personal/i)).toBeInTheDocument();
284
+ expect(screen.getByLabelText('admin')).toBeInTheDocument();
285
+ expect(screen.getByRole('button')).toBeInTheDocument();
286
+ });
287
+ });
288
+ it('should handle multiple dossier cards with different data', async () => {
289
+ const dossiers = [
290
+ createMockDossier({ dossier_id: 'dossier-1', title: 'Dossier 1', type: 'personal' }),
291
+ createMockDossier({ dossier_id: 'dossier-2', title: 'Dossier 2', type: 'global' }),
292
+ createMockDossier({ dossier_id: 'dossier-3', title: 'Dossier 3', type: 'personal' })
293
+ ];
294
+ render(_jsx(Wrapper, { children: dossiers.map(dossier => (_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }, dossier.dossier_id))) }));
295
+ await waitFor(() => {
296
+ expect(screen.getByText('Dossier 1')).toBeInTheDocument();
297
+ expect(screen.getByText('Dossier 2')).toBeInTheDocument();
298
+ expect(screen.getByText('Dossier 3')).toBeInTheDocument();
299
+ expect(screen.getAllByRole('button')).toHaveLength(3);
300
+ });
301
+ });
302
+ it('should work with different owners', async () => {
303
+ const owners = ['user1', 'user2', 'admin'];
304
+ const { rerender } = render(_jsx(DossierCard, { dossier: createMockDossier({ owner: owners[0] }) }), {
305
+ wrapper: Wrapper
306
+ });
307
+ for (const owner of owners) {
308
+ rerender(_jsx(DossierCard, { dossier: createMockDossier({ owner }) }));
309
+ await waitFor(() => {
310
+ expect(screen.getByLabelText(owner)).toBeInTheDocument();
311
+ });
312
+ }
313
+ });
314
+ });
315
+ describe('Accessibility', () => {
316
+ it('should have accessible delete button', async () => {
317
+ const dossier = createMockDossier();
318
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
319
+ await waitFor(() => {
320
+ const deleteButton = screen.getByRole('button');
321
+ expect(deleteButton).toBeInTheDocument();
322
+ expect(deleteButton).toHaveAccessibleName();
323
+ });
324
+ });
325
+ it('should have tooltips for icons', async () => {
326
+ const dossier = createMockDossier({ type: 'personal' });
327
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
328
+ await waitFor(() => {
329
+ // Type icon tooltip
330
+ expect(screen.getByLabelText(/personal/i)).toBeInTheDocument();
331
+ // Delete button tooltip
332
+ expect(screen.getByLabelText(/delete/i)).toBeInTheDocument();
333
+ });
334
+ });
335
+ it('should have proper semantic HTML structure', async () => {
336
+ const dossier = createMockDossier();
337
+ const { container } = render(_jsx(DossierCard, { dossier: dossier }), { wrapper: Wrapper });
338
+ await waitFor(() => {
339
+ // Should have a card
340
+ expect(container.querySelector('.MuiCard-root')).toBeInTheDocument();
341
+ // Should have code element for query
342
+ expect(container.querySelector('code')).toBeInTheDocument();
343
+ });
344
+ });
345
+ it('should maintain focus on delete button', async () => {
346
+ const dossier = createMockDossier();
347
+ render(_jsx(DossierCard, { dossier: dossier, onDelete: mockOnDelete }), { wrapper: Wrapper });
348
+ await waitFor(() => {
349
+ expect(screen.getByRole('button')).toBeInTheDocument();
350
+ });
351
+ const deleteButton = screen.getByRole('button');
352
+ act(() => {
353
+ deleteButton.focus();
354
+ });
355
+ await waitFor(() => {
356
+ expect(deleteButton).toHaveFocus();
357
+ });
358
+ });
359
+ });
360
+ describe('Prop Changes', () => {
361
+ it('should update when dossier prop changes', async () => {
362
+ const { rerender } = render(_jsx(DossierCard, { dossier: createMockDossier({ title: 'Original' }) }), {
363
+ wrapper: Wrapper
364
+ });
365
+ await waitFor(() => {
366
+ expect(screen.getByText('Original')).toBeInTheDocument();
367
+ });
368
+ rerender(_jsx(DossierCard, { dossier: createMockDossier({ title: 'Updated' }) }));
369
+ await waitFor(() => {
370
+ expect(screen.getByText('Updated')).toBeInTheDocument();
371
+ expect(screen.queryByText('Original')).not.toBeInTheDocument();
372
+ });
373
+ });
374
+ it('should update when onDelete prop is added', async () => {
375
+ const { rerender } = render(_jsx(DossierCard, { dossier: createMockDossier() }), { wrapper: Wrapper });
376
+ await waitFor(() => {
377
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
378
+ });
379
+ rerender(_jsx(DossierCard, { dossier: createMockDossier(), onDelete: mockOnDelete }));
380
+ await waitFor(() => {
381
+ expect(screen.getByRole('button')).toBeInTheDocument();
382
+ });
383
+ });
384
+ it('should update when onDelete prop is removed', async () => {
385
+ const { rerender } = render(_jsx(DossierCard, { dossier: createMockDossier(), onDelete: mockOnDelete }), {
386
+ wrapper: Wrapper
387
+ });
388
+ await waitFor(() => {
389
+ expect(screen.getByRole('button')).toBeInTheDocument();
390
+ });
391
+ rerender(_jsx(DossierCard, { dossier: createMockDossier() }));
392
+ await waitFor(() => {
393
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
394
+ });
395
+ });
396
+ it('should update when className changes', async () => {
397
+ const { container, rerender } = render(_jsx(DossierCard, { dossier: createMockDossier(), className: "class-1" }), {
398
+ wrapper: Wrapper
399
+ });
400
+ await waitFor(() => {
401
+ expect(container.querySelector('.class-1')).toBeInTheDocument();
402
+ });
403
+ rerender(_jsx(DossierCard, { dossier: createMockDossier(), className: "class-2" }));
404
+ await waitFor(() => {
405
+ expect(container.querySelector('.class-2')).toBeInTheDocument();
406
+ expect(container.querySelector('.class-1')).not.toBeInTheDocument();
407
+ });
408
+ });
409
+ });
410
+ });
package/package.json CHANGED
@@ -96,7 +96,7 @@
96
96
  "internal-slot": "1.0.7"
97
97
  },
98
98
  "type": "module",
99
- "version": "2.15.0-dev.333",
99
+ "version": "2.15.0-dev.335",
100
100
  "exports": {
101
101
  "./i18n": "./i18n.js",
102
102
  "./index.css": "./index.css",
package/tests/utils.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Action } from '@cccsaurora/howler-ui/models/entities/generated/Action';
2
2
  import type { Analytic } from '@cccsaurora/howler-ui/models/entities/generated/Analytic';
3
+ import type { Dossier } from '@cccsaurora/howler-ui/models/entities/generated/Dossier';
3
4
  import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
4
5
  import type { Template } from '@cccsaurora/howler-ui/models/entities/generated/Template';
5
6
  import type { View } from '@cccsaurora/howler-ui/models/entities/generated/View';
@@ -11,4 +12,5 @@ export declare const createMockAnalytic: (overrides?: Partial<Analytic>) => Anal
11
12
  export declare const createMockTemplate: (overrides?: Partial<Template>) => Template;
12
13
  export declare const createMockAction: (overrides?: Partial<Action>) => Action;
13
14
  export declare const createMockView: (overrides?: Partial<View>) => View;
15
+ export declare const createMockDossier: (overrides?: Partial<Dossier>) => Dossier;
14
16
  export {};
package/tests/utils.js CHANGED
@@ -52,3 +52,12 @@ export const createMockView = (overrides) => ({
52
52
  },
53
53
  ...overrides
54
54
  });
55
+ // Helper function to create mock dossiers
56
+ export const createMockDossier = (overrides) => ({
57
+ dossier_id: 'test-dossier-id',
58
+ title: 'Test Dossier',
59
+ query: 'howler.status:open',
60
+ type: 'personal',
61
+ owner: 'test-user',
62
+ ...overrides
63
+ });