@tuturuuu/ui 0.5.0 → 0.6.0

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 (88) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +41 -34
  3. package/src/components/ui/currency-input.tsx +65 -23
  4. package/src/components/ui/custom/__tests__/sidebar-context.test.tsx +64 -0
  5. package/src/components/ui/custom/__tests__/sidebar-remote-behavior-bridge.test.tsx +109 -0
  6. package/src/components/ui/custom/combobox.test.tsx +141 -0
  7. package/src/components/ui/custom/combobox.tsx +105 -36
  8. package/src/components/ui/custom/settings/task-settings.tsx +50 -0
  9. package/src/components/ui/custom/settings/task-sound-settings.test.tsx +21 -1
  10. package/src/components/ui/custom/sidebar-context.tsx +68 -6
  11. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +21 -2
  12. package/src/components/ui/finance/finance-layout.tsx +2 -4
  13. package/src/components/ui/finance/shared/balance-mode-toggle.tsx +35 -0
  14. package/src/components/ui/finance/shared/finance-layout-controls.tsx +43 -0
  15. package/src/components/ui/finance/shared/quick-actions.tsx +14 -6
  16. package/src/components/ui/finance/shared/use-finance-balance-mode.ts +72 -0
  17. package/src/components/ui/finance/shared/wallet-balance-mode.test.ts +66 -0
  18. package/src/components/ui/finance/shared/wallet-balance-mode.ts +42 -0
  19. package/src/components/ui/finance/transactions/form-types.ts +23 -0
  20. package/src/components/ui/finance/transactions/form.tsx +81 -22
  21. package/src/components/ui/finance/transactions/wallet-filter.tsx +21 -2
  22. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-adjustment-dialog.tsx +73 -26
  23. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-history-dialog.tsx +617 -0
  24. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-panel.tsx +2 -1
  25. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoint-sections.tsx +4 -4
  26. package/src/components/ui/finance/wallets/checkpoints/wallet-checkpoints.test.tsx +298 -34
  27. package/src/components/ui/finance/wallets/checkpoints/wallet-total-check-dialog.tsx +219 -46
  28. package/src/components/ui/finance/wallets/columns-rendering.test.tsx +125 -0
  29. package/src/components/ui/finance/wallets/columns.test.ts +56 -0
  30. package/src/components/ui/finance/wallets/columns.tsx +196 -43
  31. package/src/components/ui/finance/wallets/form.test.tsx +79 -14
  32. package/src/components/ui/finance/wallets/form.tsx +41 -197
  33. package/src/components/ui/finance/wallets/query-invalidation.ts +1 -0
  34. package/src/components/ui/finance/wallets/wallet-basics-fields.tsx +141 -0
  35. package/src/components/ui/finance/wallets/wallet-credit-fields.tsx +136 -0
  36. package/src/components/ui/finance/wallets/walletId/credit-wallet-summary.tsx +143 -68
  37. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.test.tsx +105 -0
  38. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +120 -16
  39. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.test.tsx +64 -0
  40. package/src/components/ui/finance/wallets/walletId/wallet-details-amount.tsx +226 -6
  41. package/src/components/ui/finance/wallets/walletId/wallet-details-page.test.tsx +64 -2
  42. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +42 -35
  43. package/src/components/ui/finance/wallets/wallets-data-table.test.tsx +171 -0
  44. package/src/components/ui/finance/wallets/wallets-data-table.tsx +132 -29
  45. package/src/components/ui/finance/wallets/wallets-page.test.tsx +111 -37
  46. package/src/components/ui/finance/wallets/wallets-page.tsx +38 -78
  47. package/src/components/ui/storefront/accent-button.tsx +33 -0
  48. package/src/components/ui/storefront/cart-summary.tsx +140 -0
  49. package/src/components/ui/storefront/empty-listings.tsx +32 -0
  50. package/src/components/ui/storefront/hero-panel.tsx +70 -0
  51. package/src/components/ui/storefront/image-panel.tsx +40 -0
  52. package/src/components/ui/storefront/index.ts +12 -0
  53. package/src/components/ui/storefront/listing-card.tsx +129 -0
  54. package/src/components/ui/storefront/storefront-surface.test.tsx +85 -0
  55. package/src/components/ui/storefront/storefront-surface.tsx +235 -0
  56. package/src/components/ui/storefront/types.ts +99 -0
  57. package/src/components/ui/storefront/utils.ts +90 -0
  58. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +134 -0
  59. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +127 -0
  60. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +17 -42
  61. package/src/components/ui/tu-do/boards/boardId/timeline-board-open-task.test.tsx +164 -0
  62. package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +25 -16
  63. package/src/components/ui/tu-do/hooks/useTaskDialog.ts +15 -1
  64. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +2 -0
  65. package/src/components/ui/tu-do/my-tasks/use-task-context-actions.ts +114 -7
  66. package/src/components/ui/tu-do/providers/__tests__/task-dialog-provider.test.tsx +217 -5
  67. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +180 -35
  68. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +222 -26
  69. package/src/components/ui/tu-do/shared/board-client.tsx +1 -3
  70. package/src/components/ui/tu-do/shared/list-view-context-menu.test.tsx +55 -2
  71. package/src/components/ui/tu-do/shared/list-view.tsx +23 -16
  72. package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +93 -76
  73. package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +11 -0
  74. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +128 -1
  75. package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +104 -69
  76. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.test.tsx +129 -0
  77. package/src/components/ui/tu-do/shared/task-edit-dialog/components/smart-task-suggestions-panel.tsx +358 -0
  78. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +1 -1
  79. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +6 -2
  80. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.test.tsx +17 -1
  81. package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-name-input.tsx +151 -111
  82. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-form-reset.ts +18 -2
  83. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-realtime-sync.ts +1 -2
  84. package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +5 -3
  85. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +584 -53
  86. package/src/hooks/useBoardRealtime.ts +54 -1
  87. package/src/hooks/useBoardRealtimeEventHandler.ts +169 -4
  88. package/src/hooks/useTaskUserRealtime.ts +338 -0
@@ -1,32 +1,42 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
2
  import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
- import type { ReactElement } from 'react';
3
+ import type { ReactElement, ReactNode } from 'react';
4
4
  import { beforeEach, describe, expect, it, vi } from 'vitest';
5
5
  import { WalletCheckpointAdjustmentDialog } from './wallet-checkpoint-adjustment-dialog';
6
6
  import { WalletCheckpointAmount } from './wallet-checkpoint-amount';
7
+ import { WalletCheckpointHistoryDialog } from './wallet-checkpoint-history-dialog';
7
8
  import { WalletCheckpointPanel } from './wallet-checkpoint-panel';
8
9
  import { WalletTotalCheckDialog } from './wallet-total-check-dialog';
9
10
 
10
11
  const mocks = vi.hoisted(() => ({
11
- createTransaction: vi.fn(),
12
12
  createWalletCheckpointBatch: vi.fn(),
13
+ createWalletCheckpointReconciliation: vi.fn(),
14
+ defaultReconciliationCategoryId: '',
13
15
  deleteWalletCheckpoint: vi.fn(),
16
+ getWalletCheckpointHistory: vi.fn(),
14
17
  isConfidential: true,
18
+ listWallets: vi.fn(),
15
19
  listTransactionCategories: vi.fn(),
16
20
  listWalletCheckpoints: vi.fn(),
17
21
  success: vi.fn(),
18
22
  }));
19
23
 
20
24
  vi.mock('@tuturuuu/internal-api/finance', () => ({
21
- createTransaction: (...args: Parameters<typeof mocks.createTransaction>) =>
22
- mocks.createTransaction(...args),
23
25
  createWalletCheckpoint: vi.fn(),
24
26
  createWalletCheckpointBatch: (
25
27
  ...args: Parameters<typeof mocks.createWalletCheckpointBatch>
26
28
  ) => mocks.createWalletCheckpointBatch(...args),
29
+ createWalletCheckpointReconciliation: (
30
+ ...args: Parameters<typeof mocks.createWalletCheckpointReconciliation>
31
+ ) => mocks.createWalletCheckpointReconciliation(...args),
27
32
  deleteWalletCheckpoint: (
28
33
  ...args: Parameters<typeof mocks.deleteWalletCheckpoint>
29
34
  ) => mocks.deleteWalletCheckpoint(...args),
35
+ getWalletCheckpointHistory: (
36
+ ...args: Parameters<typeof mocks.getWalletCheckpointHistory>
37
+ ) => mocks.getWalletCheckpointHistory(...args),
38
+ listWallets: (...args: Parameters<typeof mocks.listWallets>) =>
39
+ mocks.listWallets(...args),
30
40
  listTransactionCategories: (
31
41
  ...args: Parameters<typeof mocks.listTransactionCategories>
32
42
  ) => mocks.listTransactionCategories(...args),
@@ -36,6 +46,40 @@ vi.mock('@tuturuuu/internal-api/finance', () => ({
36
46
  updateWalletCheckpoint: vi.fn(),
37
47
  }));
38
48
 
49
+ vi.mock('@tuturuuu/ui/hooks/use-workspace-config', () => ({
50
+ useWorkspaceConfig: () => ({
51
+ data: mocks.defaultReconciliationCategoryId,
52
+ isLoading: false,
53
+ }),
54
+ }));
55
+
56
+ vi.mock('@tuturuuu/ui/select', () => ({
57
+ Select: ({
58
+ children,
59
+ onValueChange,
60
+ value,
61
+ }: {
62
+ children: ReactNode;
63
+ onValueChange: (value: string) => void;
64
+ value: string;
65
+ }) => (
66
+ <select
67
+ onChange={(event) => onValueChange(event.target.value)}
68
+ value={value}
69
+ >
70
+ {children}
71
+ </select>
72
+ ),
73
+ SelectContent: ({ children }: { children: ReactNode }) => children,
74
+ SelectItem: ({ children, value }: { children: ReactNode; value: string }) => (
75
+ <option value={value}>
76
+ {typeof children === 'string' ? children : value}
77
+ </option>
78
+ ),
79
+ SelectTrigger: ({ children }: { children: ReactNode }) => children,
80
+ SelectValue: () => null,
81
+ }));
82
+
39
83
  vi.mock('@tuturuuu/ui/sonner', () => ({
40
84
  toast: {
41
85
  error: vi.fn(),
@@ -72,12 +116,29 @@ function renderWithQueryClient(ui: ReactElement) {
72
116
  describe('wallet checkpoint UI', () => {
73
117
  beforeEach(() => {
74
118
  vi.clearAllMocks();
119
+ mocks.defaultReconciliationCategoryId = '';
75
120
  mocks.isConfidential = true;
76
- mocks.createTransaction.mockResolvedValue({ transaction_id: 'tx-1' });
121
+ mocks.createWalletCheckpointReconciliation.mockResolvedValue({
122
+ checked_at: '2026-06-11T10:00:00.000Z',
123
+ checkpoint_id: 'checkpoint-1',
124
+ created: true,
125
+ offset_amount: -12.34,
126
+ transaction_id: 'tx-1',
127
+ wallet_id: 'wallet-1',
128
+ });
77
129
  mocks.createWalletCheckpointBatch.mockResolvedValue({
78
130
  data: [],
79
131
  totals_by_currency: [],
80
132
  });
133
+ mocks.getWalletCheckpointHistory.mockResolvedValue({
134
+ audit_statuses: [],
135
+ checkpoints: [],
136
+ intervals: [],
137
+ latest_checkpoints: [],
138
+ totals_by_currency: [],
139
+ wallets: [],
140
+ });
141
+ mocks.listWallets.mockResolvedValue([]);
81
142
  mocks.listTransactionCategories.mockResolvedValue([]);
82
143
  mocks.listWalletCheckpoints.mockResolvedValue({
83
144
  data: [],
@@ -101,32 +162,37 @@ describe('wallet checkpoint UI', () => {
101
162
  });
102
163
 
103
164
  it('saves all-wallet checks with typed decimal values intact', async () => {
165
+ mocks.listWallets.mockResolvedValue([
166
+ {
167
+ balance: 0,
168
+ currency: 'USD',
169
+ id: 'wallet-1',
170
+ name: 'Cash',
171
+ },
172
+ {
173
+ balance: 0,
174
+ currency: 'VND',
175
+ id: 'wallet-2',
176
+ name: 'Bank',
177
+ },
178
+ ]);
179
+
104
180
  renderWithQueryClient(
105
- <WalletTotalCheckDialog
106
- wsId="ws-1"
107
- canUpdateWallets
108
- wallets={[
109
- {
110
- balance: 0,
111
- currency: 'USD',
112
- id: 'wallet-1',
113
- name: 'Cash',
114
- },
115
- {
116
- balance: 0,
117
- currency: 'VND',
118
- id: 'wallet-2',
119
- name: 'Bank',
120
- },
121
- ]}
122
- />
181
+ <WalletTotalCheckDialog wsId="ws-1" canUpdateWallets currency="USD" />
123
182
  );
124
183
 
184
+ expect(mocks.listWallets).not.toHaveBeenCalled();
185
+
125
186
  fireEvent.click(screen.getByRole('button', { name: 'all_wallet_check' }));
187
+ await waitFor(() => {
188
+ expect(mocks.listWallets).toHaveBeenCalledWith('ws-1');
189
+ });
126
190
  expect(
127
191
  screen.getByRole('button', { name: 'save_checkpoints' })
128
192
  ).toBeDisabled();
129
193
 
194
+ await screen.findByLabelText('actual_balance_with_currency:USD');
195
+
130
196
  fireEvent.change(
131
197
  screen.getByLabelText('actual_balance_with_currency:USD'),
132
198
  {
@@ -236,7 +302,7 @@ describe('wallet checkpoint UI', () => {
236
302
  expect(await screen.findByText('clean')).toBeInTheDocument();
237
303
  expect(screen.getByText('unresolved')).toBeInTheDocument();
238
304
  expect(
239
- screen.getByRole('button', { name: 'create_adjustment' })
305
+ screen.getByRole('button', { name: 'reconcile' })
240
306
  ).toBeInTheDocument();
241
307
  expect(
242
308
  screen.getByRole('button', { name: 'edit_checkpoint' })
@@ -246,11 +312,12 @@ describe('wallet checkpoint UI', () => {
246
312
  ).toBeInTheDocument();
247
313
  });
248
314
 
249
- it('creates adjustment transactions with exact signed variance and reports disabled', async () => {
315
+ it('submits checkpoint reconciliations through the recomputing endpoint', async () => {
250
316
  renderWithQueryClient(
251
317
  <WalletCheckpointAdjustmentDialog
252
318
  wsId="ws-1"
253
319
  walletId="wallet-1"
320
+ checkpointId="checkpoint-1"
254
321
  walletName="Cash"
255
322
  checkedAt="2026-06-11T10:00:00.000Z"
256
323
  currency="USD"
@@ -261,17 +328,214 @@ describe('wallet checkpoint UI', () => {
261
328
  />
262
329
  );
263
330
 
264
- fireEvent.click(screen.getByRole('button', { name: 'create_adjustment' }));
331
+ const reconcileButton = screen.getByRole('button', { name: 'reconcile' });
332
+ await waitFor(() => expect(reconcileButton).not.toBeDisabled());
333
+ fireEvent.click(reconcileButton);
265
334
 
266
335
  await waitFor(() => {
267
- expect(mocks.createTransaction).toHaveBeenCalledWith('ws-1', {
268
- amount: -12.34,
269
- category_id: undefined,
270
- description: expect.stringContaining('Cash'),
271
- origin_wallet_id: 'wallet-1',
272
- report_opt_in: false,
273
- taken_at: expect.any(String),
274
- });
336
+ expect(mocks.createWalletCheckpointReconciliation).toHaveBeenCalledWith(
337
+ 'ws-1',
338
+ 'wallet-1',
339
+ 'checkpoint-1',
340
+ expect.objectContaining({
341
+ category_id: undefined,
342
+ description: expect.stringContaining('Cash'),
343
+ })
344
+ );
345
+ });
346
+ });
347
+
348
+ it('preselects the configured reconciliation category', async () => {
349
+ mocks.defaultReconciliationCategoryId = 'category-reconcile';
350
+ mocks.listTransactionCategories.mockResolvedValue([
351
+ {
352
+ id: 'category-reconcile',
353
+ name: 'Audit',
354
+ },
355
+ ]);
356
+
357
+ renderWithQueryClient(
358
+ <WalletCheckpointAdjustmentDialog
359
+ wsId="ws-1"
360
+ walletId="wallet-1"
361
+ checkpointId="checkpoint-1"
362
+ walletName="Cash"
363
+ checkedAt="2026-06-11T10:00:00.000Z"
364
+ currency="USD"
365
+ variance={-12.34}
366
+ open
367
+ onOpenChange={vi.fn()}
368
+ onCreated={vi.fn()}
369
+ />
370
+ );
371
+
372
+ const reconcileButton = screen.getByRole('button', { name: 'reconcile' });
373
+ await waitFor(() => expect(reconcileButton).not.toBeDisabled());
374
+ fireEvent.click(reconcileButton);
375
+
376
+ await waitFor(() => {
377
+ expect(mocks.createWalletCheckpointReconciliation).toHaveBeenCalledWith(
378
+ 'ws-1',
379
+ 'wallet-1',
380
+ 'checkpoint-1',
381
+ expect.objectContaining({
382
+ category_id: 'category-reconcile',
383
+ })
384
+ );
385
+ });
386
+ });
387
+
388
+ it('submits no reconciliation category after clearing the configured default', async () => {
389
+ mocks.defaultReconciliationCategoryId = 'category-reconcile';
390
+ mocks.listTransactionCategories.mockResolvedValue([
391
+ {
392
+ id: 'category-reconcile',
393
+ name: 'Audit',
394
+ },
395
+ ]);
396
+
397
+ renderWithQueryClient(
398
+ <WalletCheckpointAdjustmentDialog
399
+ wsId="ws-1"
400
+ walletId="wallet-1"
401
+ checkpointId="checkpoint-1"
402
+ walletName="Cash"
403
+ checkedAt="2026-06-11T10:00:00.000Z"
404
+ currency="USD"
405
+ variance={-12.34}
406
+ open
407
+ onOpenChange={vi.fn()}
408
+ onCreated={vi.fn()}
409
+ />
410
+ );
411
+
412
+ const reconcileButton = screen.getByRole('button', { name: 'reconcile' });
413
+ await waitFor(() => expect(reconcileButton).not.toBeDisabled());
414
+ fireEvent.change(screen.getByRole('combobox'), {
415
+ target: { value: 'none' },
416
+ });
417
+ fireEvent.click(reconcileButton);
418
+
419
+ await waitFor(() => {
420
+ expect(mocks.createWalletCheckpointReconciliation).toHaveBeenCalledWith(
421
+ 'ws-1',
422
+ 'wallet-1',
423
+ 'checkpoint-1',
424
+ expect.objectContaining({
425
+ category_id: undefined,
426
+ })
427
+ );
428
+ });
429
+ });
430
+
431
+ it('renders checkpoint history and submits interval reconciliations with interval basis', async () => {
432
+ mocks.isConfidential = false;
433
+ mocks.getWalletCheckpointHistory.mockResolvedValue({
434
+ audit_statuses: [
435
+ {
436
+ audited_balance: 120,
437
+ checkpoint_ledger_balance: 110,
438
+ latest_actual_balance: 120,
439
+ latest_checked_at: '2026-06-11T10:00:00.000Z',
440
+ latest_checkpoint_id: 'checkpoint-2',
441
+ ledger_balance: 115,
442
+ post_checkpoint_delta: 0,
443
+ post_checkpoint_transaction_count: 0,
444
+ status: 'unresolved',
445
+ variance: 5,
446
+ wallet_id: 'wallet-1',
447
+ },
448
+ ],
449
+ checkpoints: [
450
+ {
451
+ actual_balance: 120,
452
+ checked_at: '2026-06-11T10:00:00.000Z',
453
+ created_at: '2026-06-11T10:01:00.000Z',
454
+ created_by: 'user-1',
455
+ currency: 'USD',
456
+ current_ledger_balance: 115,
457
+ current_variance: 5,
458
+ id: 'checkpoint-2',
459
+ ledger_balance: 110,
460
+ note: 'Audit',
461
+ original_variance: 10,
462
+ updated_at: '2026-06-11T10:01:00.000Z',
463
+ wallet_id: 'wallet-1',
464
+ },
465
+ ],
466
+ intervals: [
467
+ {
468
+ actual_delta: 20,
469
+ currency: 'USD',
470
+ end_actual_balance: 120,
471
+ end_checked_at: '2026-06-11T10:00:00.000Z',
472
+ end_checkpoint_id: 'checkpoint-2',
473
+ interval_variance: 5,
474
+ is_clean: false,
475
+ ledger_delta: 15,
476
+ start_actual_balance: 100,
477
+ start_checked_at: '2026-06-10T10:00:00.000Z',
478
+ start_checkpoint_id: 'checkpoint-1',
479
+ transaction_count: 3,
480
+ wallet_id: 'wallet-1',
481
+ wallet_name: 'Cash',
482
+ },
483
+ ],
484
+ latest_checkpoints: [],
485
+ totals_by_currency: [],
486
+ wallets: [
487
+ {
488
+ balance: 115,
489
+ currency: 'USD',
490
+ icon: null,
491
+ id: 'wallet-1',
492
+ image_src: null,
493
+ name: 'Cash',
494
+ type: 'STANDARD',
495
+ },
496
+ ],
497
+ });
498
+
499
+ renderWithQueryClient(
500
+ <WalletCheckpointHistoryDialog
501
+ wsId="ws-1"
502
+ financePrefix="/finance"
503
+ canCreateTransactions
504
+ />
505
+ );
506
+
507
+ fireEvent.click(screen.getByRole('button', { name: 'checkpoint_history' }));
508
+
509
+ expect(await screen.findByText('Cash')).toBeInTheDocument();
510
+ expect(screen.getByText('transaction_count')).toBeInTheDocument();
511
+
512
+ const firstReconcileButton = screen.getAllByRole('button', {
513
+ name: 'reconcile',
514
+ })[0];
515
+ if (!firstReconcileButton) {
516
+ throw new Error('Expected a reconcile button');
517
+ }
518
+ fireEvent.click(firstReconcileButton);
519
+ const reconcileButtons = screen.getAllByRole('button', {
520
+ name: 'reconcile',
521
+ });
522
+ const lastReconcileButton = reconcileButtons.at(-1);
523
+ if (!lastReconcileButton) {
524
+ throw new Error('Expected a checkpoint reconcile button');
525
+ }
526
+ await waitFor(() => expect(lastReconcileButton).not.toBeDisabled());
527
+ fireEvent.click(lastReconcileButton);
528
+
529
+ await waitFor(() => {
530
+ expect(mocks.createWalletCheckpointReconciliation).toHaveBeenCalledWith(
531
+ 'ws-1',
532
+ 'wallet-1',
533
+ 'checkpoint-2',
534
+ expect.objectContaining({
535
+ category_id: undefined,
536
+ description: expect.stringContaining('Cash'),
537
+ })
538
+ );
275
539
  });
276
540
  });
277
541
  });