diff --git a/resources/js/services/transaction-sync.test.ts b/resources/js/services/transaction-sync.test.ts new file mode 100644 index 00000000..f04f8b0e --- /dev/null +++ b/resources/js/services/transaction-sync.test.ts @@ -0,0 +1,55 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { transactionSyncService } from './transaction-sync'; + +const dbMock = vi.hoisted(() => ({ + transactions: { + delete: vi.fn(async () => undefined), + }, + sync_metadata: { delete: vi.fn(), get: vi.fn(), put: vi.fn() }, +})); + +const axiosMock = vi.hoisted(() => ({ + delete: vi.fn(async () => ({ data: {} })), +})); + +// Keep the real withDb (reads globalThis live); swap only the Dexie-backed db. +vi.mock('@/lib/dexie-db', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, db: dbMock }; +}); + +vi.mock('axios', () => ({ default: axiosMock })); + +describe('transactionSyncService.delete', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('deletes via the API but skips the cache eviction when IndexedDB is missing', async () => { + vi.stubGlobal('indexedDB', undefined); + + await expect( + transactionSyncService.delete('txn-1'), + ).resolves.toBeUndefined(); + + expect(axiosMock.delete).toHaveBeenCalledWith('/transactions/txn-1', { + data: undefined, + }); + expect(dbMock.transactions.delete).not.toHaveBeenCalled(); + }); + + it('deletes via the API and evicts the cache when IndexedDB is available', async () => { + vi.stubGlobal('indexedDB', {} as IDBFactory); + + await transactionSyncService.delete('txn-1'); + + expect(axiosMock.delete).toHaveBeenCalledWith('/transactions/txn-1', { + data: undefined, + }); + expect(dbMock.transactions.delete).toHaveBeenCalledWith('txn-1'); + }); +}); diff --git a/resources/js/services/transaction-sync.ts b/resources/js/services/transaction-sync.ts index 3dbd5992..61983d9b 100644 --- a/resources/js/services/transaction-sync.ts +++ b/resources/js/services/transaction-sync.ts @@ -1,4 +1,4 @@ -import { db } from '@/lib/dexie-db'; +import { db, withDb } from '@/lib/dexie-db'; import { TransactionSyncManager } from '@/lib/sync-manager'; import type { LearnedRuleNotice } from '@/types/automation-rule'; import type { Transaction } from '@/types/transaction'; @@ -192,7 +192,11 @@ class TransactionSyncService { await axios.delete(`/transactions/${id}`, { data: options?.updateBalance ? { update_balance: true } : undefined, }); - await db.transactions.delete(id); + // The API delete above is authoritative; the local cache eviction is + // best-effort and skipped when IndexedDB is unavailable (PHP-LARAVEL-43). + await withDb(async () => { + await db.transactions.delete(id); + }, undefined); } async updateManyIndividual(