fix: lists and list items deleted on another device are now deleted locally

This commit is contained in:
2026-08-21 22:53:18 +02:00
parent b3ee69686e
commit 61d1b84fff
2 changed files with 83 additions and 0 deletions
+52
View File
@@ -123,6 +123,9 @@ describe('useListsStore', () => {
it('creates a list locally, queues a sync entry, and remaps the id after a successful sync', async () => {
listsApiMocks.createListApi.mockResolvedValueOnce({ id: 'server-id-1', name: 'Groceries' })
// The chained pullFromServer() call needs to report the just-created list
// back, otherwise it would look like the server deleted it.
listsApiMocks.getListsApi.mockResolvedValueOnce([{ id: 'server-id-1', name: 'Groceries' }])
const store = useListsStore()
const localList = await store.createList('Groceries', [])
@@ -264,6 +267,55 @@ describe('useListsStore', () => {
expect(store.listItems.find((item) => item.id === 'server-item-1')).toBeDefined()
})
it('deletes a previously synced list locally when it is missing from the server', async () => {
const store = useListsStore()
await fakeDb.lists.put({ id: 'server-list-1', name: 'Groceries', pendingSync: false })
await fakeDb.listItems.put({
id: 'server-item-1',
list_id: 'server-list-1',
title: 'Milk',
pendingSync: false,
})
await store.refresh()
listsApiMocks.getListsApi.mockResolvedValueOnce([])
await store.pullFromServer()
expect(store.lists.find((list) => list.id === 'server-list-1')).toBeUndefined()
expect(store.listItems.find((item) => item.id === 'server-item-1')).toBeUndefined()
})
it('deletes a previously synced list item locally when it is missing from the server', async () => {
const store = useListsStore()
await fakeDb.listItems.put({
id: 'server-item-2',
list_id: 'server-list-1',
title: 'Bread',
pendingSync: false,
})
await store.refresh()
listsApiMocks.getListItemsApi.mockResolvedValueOnce([])
await store.pullListItems('server-list-1')
expect(store.listItems.find((item) => item.id === 'server-item-2')).toBeUndefined()
})
it('does not delete a locally pending list even when it is missing from the server', async () => {
listsApiMocks.createListApi.mockImplementation(() => new Promise(() => {}))
const store = useListsStore()
const localList = await store.createList('Local only')
listsApiMocks.getListsApi.mockResolvedValueOnce([])
await store.pullFromServer()
expect(store.lists.find((list) => list.id === localList.id)).toBeDefined()
})
it('does not overwrite a locally pending list with stale server data', async () => {
listsApiMocks.createListApi.mockImplementation(() => new Promise(() => {}))
+31
View File
@@ -369,6 +369,7 @@ export const useListsStore = defineStore('lists', () => {
try {
const serverLists = await getListsApi()
const serverListIds = new Set(serverLists.map((serverList) => serverList.id))
for (const serverList of serverLists) {
const existingList = await db.lists.get(serverList.id)
@@ -376,6 +377,23 @@ export const useListsStore = defineStore('lists', () => {
await db.lists.put({ ...serverList, pendingSync: false })
}
}
// Lists that were already synced but are no longer reported by the
// server have been deleted there (e.g. from another device), so
// remove them locally too, along with their items. Lists that still
// have pending local changes (not yet synced, e.g. a not-yet-pushed
// "createList") are left alone since the server doesn't know about
// them yet.
const localLists = await db.lists.toArray()
for (const localList of localLists) {
if (!localList.pendingSync && !serverListIds.has(localList.id)) {
await db.lists.delete(localList.id)
const orphanedItems = await db.listItems.where('list_id').equals(localList.id).toArray()
for (const item of orphanedItems) {
await db.listItems.delete(item.id)
}
}
}
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to load lists from server'
} finally {
@@ -391,12 +409,25 @@ export const useListsStore = defineStore('lists', () => {
try {
const serverItems = await getListItemsApi(listId)
const serverItemIds = new Set(serverItems.map((serverItem) => serverItem.id))
for (const serverItem of serverItems) {
const existingItem = await db.listItems.get(serverItem.id)
if (!existingItem || !existingItem.pendingSync) {
await db.listItems.put({ ...serverItem, pendingSync: false })
}
}
// Items that were already synced but are no longer reported by the
// server for this list have been deleted there, so remove them
// locally too. Items with pending local changes are left alone since
// the server doesn't know about them yet.
const localItems = await db.listItems.where('list_id').equals(listId).toArray()
for (const localItem of localItems) {
if (!localItem.pendingSync && !serverItemIds.has(localItem.id)) {
await db.listItems.delete(localItem.id)
}
}
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to load list items from server'
} finally {